Javascript基础知识整理—2

1. 节流和防抖的实现

https://blog.csdn.net/weixin_45709829/article/details/123910592

防抖(Debounce)
在设定的n秒内只会执行最新的函数
防抖实现
立即执行和非立即执行

// 支持立即执行,取消执行且返回值为fn执行结果的防抖函数
// 添加immediate表示区分立即执行和非立即执行
// 立即执行:
function Debounce(fn, wait, immediate) {
    let timer;
    // flag为真时立即执行
    let flag = true

    if (immediate) {
        resultFun = function() {
            let context = this;

            if (timer) clearTimeout(timer);

            if (flag) {
                fn.apply(context, ...arguments);
                flag = false;
            }

            timer = setTimeout(() => {
                flag = true;
            }, wait);
        }

        resultFun.cancel = function() {
            console.log('立即取消等待')
            clearTimeout(timer);
            flag = true;
        }
    } else {
        resultFun = function() {
            let context = this;

            if (timer) clearTimeout(timer);

            timer = setTimeout(() => {
                fn.apply(context, ...arguments);
            }, wait);
        }

        resultFun.cancel = function() {
            console.log('立即取消等待')
            clearTimeout(timer);
        }
    }

    return resultFun;
}

节流(Throttle)
在设定的n秒内只会执行一次函数

// 目的是在于维护一个定时器
function throttle(fn, wait) {
	let timer;
	let startTime = Date.now();

	return function() {
		var args = arguments;
		var context = this;
		var remainTime = wait - (Date.now() - startTime)

		clearTimeout(timer);
		if (remainTime <= 0) {
			fn.apply(context, args);
			startTime = Date.now();
		} else {
			timer = setTimeout(() => fn.apply(context, args), remainTime);
		}
	}

}
2. JS实现粘性定位和滚动定位

初始加载页面实现元素滚动
在这里插入图片描述
clientWidth:目标元素的宽度,包含width和内边距,不包含边框和外边距
scrollWidth:元素的内容宽度或者元素本身的宽度,两者之间的大者,有滚动条,返回的是滚动区域的整体宽度
每个元素在滚动区域内都存在一个最大的可滚动值,超出数值范围外不会再进行滚动
offsetLeft:元素左边框相较于其父元素的左偏移量,元素的margin距离其父元素的margin
offsetParent:返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table,td,th,body元素。当元素的 style.display 设置为 “none” 时,offsetParent 返回 null
Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。

实现粘性定位

3. 点击元素实现自动滚动到目标区域

实现元素动画无限循环
平滑滚动

Element.getBoundingClientRect():
方法返回一个DOMRect对象,包含该元素的大小(内边距和边框)以及相对于视口的位置

window.requestAnimationFrame(useCallback):
告诉浏览器希望在下一次重绘前使用传入的回调函数更新动画
为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

其返回值:
你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

<script>
	var e = document.getElementById("e");
    var flag = true;
    var left = 0;
    var reqId = null;

	function render() {
		if (flag) {
			if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
		} else {
			if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
		}
	}

	// 下一次重绘之前更新动画帧所调用的函数 (即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。
	(function animalFoo(time) {
		render();
		rafId = requestAnimationFrame(animloop);
		
		// 如果 left = 50则停止动画
		if (left === 50) {
			window.cancelAnimationFrame(rafId);
		}
	})()
<script>
4. 修饰滚动条

参考ui-component中的dumi主题
在这里插入图片描述

5. 实现复制粘贴

自动复制元素

let url = document.querySelector('input');
url.select(); // 选择对象
document.execCommand('Copy'); // 执行浏览器复制命令
6. xss,csrf安全

csrf:跨站点请求伪造
https://blog.csdn.net/stpeace/article/details/53512283

  1. 用户访问了A网站,并且通过正确的用户名和密码登陆进了A网站,其身份信息被保存到了cookie中
  2. 用户在未退出A网站的前提下,在同一个浏览器中打开了B网站
  3. B网站返回攻击性代码,并且发出请求访问第三方网站A网站
  4. 根据B网站的请求,在用户不知情的情况下携带了cookie信息访问了A网站,而A网站由于有了cookie信息的识别所以进行了响应的操作,而不能分辨这个是B网站发送的恶意请求

防御CSRF攻击:

  1. 验证 HTTP Referer 字段
    根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址

  2. 在请求地址中添加 token 并验证
    可以入侵成功的原因在于携带了A站的cookie,那么可以在发送请求的url中添加不需要存入cookie的验证字符串用于服务器对于当前url的验证

  3. 在 HTTP 头中自定义属性并验证
    通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。
    然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。

xss:跨站脚本攻击
https://www.jianshu.com/p/64a413ada155

  1. 基于反射的xss:指xss攻击代码在请求的url当中,随请求提交到服务器,服务器解析后返回给客户端,XSS代码随着响应内容一起传给客户端进行解析执行。
  2. 基于存储的xss:具有攻击性的脚本被保存到了服务器端(数据库,内存,文件系统)并且可以被普通用户完整的从服务的取得并执行,从而获得了在网络上传播的能力。
  3. 基于DOM或本地的xss:可以通过 DOM来动态修改页面内容,从客户端获取 DOM中的数据并在本地执行。基于这个特性,就可以利用 JS脚本来实现 XSS漏洞的利用。

防御xss攻击:

  1. 输入过滤,对所有用户自定义输入的内容添加校验
  2. 输出转义
  3. 使用 HttpOnly Cookie:将重要的cookie标记为httponly,这样的话当浏览器向Web服务器发起请求的时就会带上cookie字段,但是在js脚本中却不能访问这个cookie,这样就避免了XSS攻击利用JavaScript的document.cookie获取cookie。
7. set和map数据结构

https://es6.ruanyifeng.com/#docs/set-map

Set():类数组结构,接收一组不会重复的值,可以通过add()添加数组项
也可以用于去除字符串中的重复值

[...new Set('ababbc')].join('')

向 Set 加入值的时候,不会发生类型转换,所以5"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身,对于+0-0认为是两个相等的数,只保留0。

另外,两个对象总是不相等的。所以Set不能用于判断两个对象是否相等

WeakSet:
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
其次,垃圾回收机制不会考虑存在于weakSet当中的值

8. 斐波拉切

https://blog.csdn.net/CUBE_lotus/article/details/124535448

// 迭代方式
function test(n){
	const a = 1, b = 1;
	if (n === 1 || n === 2) return 1for (let i = 2; i < n; i++) {
		let c = a + b;
		a = b;
		b = c
	}
	return b;
}
9. 浏览器渲染过程,重绘和回流

浏览器相关
浏览器渲染过程

  1. HTML构建DOM树
标记化(词法分析)
标记化算法 =》这个算法输入为html文本,输出为html标记,也叫标记生成器 

建树算法(语法分析)
DOM树是一个以document为根节点的多叉树,因此解析器首先会创建一个document对象。标记生成器会把每个标记的信息发送给建树器。建树器接收到相应的标记时,会创建对应的 DOM 对象。

容错机制
讲到HTML5规范,就不得不说它强大的宽容策略, 容错能力非常强,虽然大家褒贬不一,不过我想作为一名资深的前端工程师,有必要知道HTML Parser在容错方面做了哪些事情。
  1. CSS样式计算
结构化样式表
因为浏览器是无法直接识别css样式文本的,所以渲染引擎在接收到css样式文件后,第一步要做的就是将其转化为一个结构化的对象,即styleSheets
在控制台可以通过document.styleSheets来查看

标准化样式属性
度与一些css样式的数值并不容易被渲染引擎理解,所以在计算样式前需要将一些数值单位标准化
em -> px,red -> #ff0000,bold -> 700等

计算每个节点的具体样式
通过继承和层叠

  1. 生成布局树
    通过生成的DOM树和DOM样式,通过浏览器的布局系统来确定每个元素在浏览器页面中的具体位置,并添加到布局树当中

  2. 渲染过程—建立图层树
    比如3D动画如何呈现出变换效果,当元素含有层叠上下文时如何控制显示和隐藏等等。

为了解决如上所述的问题,浏览器在构建完布局树之后,还会对特定的节点进行分层,构建一棵图层树(Layer Tree)。

对于节点的图层而言,默认子节点图层属于父节点图层,但也可以将子节点图层提升为一个单独的图层

显示合成(拥有层叠上下文的节点)
普通元素设置position不为static且设置了z-index属性
元素的opacity值不为1
元素的transform值不是none

隐式合成
当层叠等级低的节点被提升为一个单独的图层后,层叠等级高于节点的所有节点都会成为一个单独的图层
  1. 生成绘制列表

  2. 生成图块和生成位图
    现在开始绘制操作,实际上在渲染进程中绘制操作是由专门的线程来完成的,这个线程叫合成线程。
    首先,合成线程将图层进行分块操作
    顺便提醒一点,渲染进程中专门维护了一个栅格化线程池,专门负责把图块转换为位图数据。
    生成的位图最后会被发送给合成线程。
    在这里插入图片描述

重绘
当DOM的修改导致了样式的变化(比如修改class),并且没有影响几何属性的。
重绘只需要重新执行计算样式以及绘制列表两部分的操作

回流(重排)

  1. 一个DOM元素的几何属性发生变化(比如行内的style样式),常见的几何属性有width、height、padding、margin、left、top、border 等等。
  2. DOM节点发生增减或者移动。
  3. 读写offset,scroll或client族属性的时候,浏览器为了获取这些值,需要进行回流操作

回流相当于重新执行了渲染进程中主线程的所有步骤,所以开销是很大的

合成(GPU加速)
还有一种情况,是直接合成。比如利用 CSS3 的transform、opacity、filter这些属性就可以实现合成的效果,也就是大家常说的GPU加速。
在合成的情况下,会直接跳过布局和绘制流程,进入非主线程的处理部分,即直接交给合成线程处理
又是在于能够充分发挥GPU的又是,且不占用主线程的资源,即使主线程卡住,也可以顺利展示元素变化。

1.避免频繁使用 style,而是采用修改class的方式。
2. 使用createDocumentFragment进行批量的 DOM 操作。
3. 对于 resize、scroll 等进行防抖/节流处理。
4. 添加 will-change: tranform ,让渲染引擎为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。当然这个变化不限于tranform, 任何可以实现合成效果的 CSS 属性都能用will-change来声明。这里有一个实际的例子,一行will-change: tranform拯救一个项目,点击直达。

10. https加密方式

https:
第 015

  1. 首先https并不是新的协议,而是http + ssl加密层
  2. 主要作用:
    对传递数据进行加密,保证传输过程中的数据安全,因为http是明文传输,所以其中的数据安全难以保证
    对网站服务器进行真实身份的验证

HTTP协议存在的问题
HTTP协议直接和TCP通信

  1. HTTP报文使用明文方法发送
  2. 无法证明报文的完整性,所以可能遭篡改(换句话说,没有任何办法确认,发出的请求/响应和接收到的请求/响应是前后相同的。)
  3. 不会验证通信方的身份,因此有可能遭遇伪装(HTTP协议中的请求和响应不会对通信方进行确认)

HTTPS
HTTP先和SSL通信,SSL再和TCP通信
HTTPS 协议的主要功能基本都依赖于 TLS/SSL 协议,TLS/SSL 的功能实现主要依赖于三类基本算法:散列函数 、对称加密和非对称加密
散列函数:验证信息的完整性
对称加密:对数据加密
非对称加密:实现身份认证和生成密钥

对称加密:
加密和解密共用一个密钥,加密同时也必须将密钥发给对方

非对称加密:
特点是传输信息一对多,服务器只需要维持一个私钥就能够和多个客户进行加密通信。
使用公开密钥加密方式,发送密文的一方使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用自己的私有密钥进行解密。

对称加密+非对称加密(HTTPS使用的方式)
在交换密钥环节使用非对称加密方式,之后的建立通信交换报文阶段则使用对称加密方式。

发送方使用接收方的公钥对称密钥进行加密
接收方使用自己的私钥对称密钥进行解密
这样就可以确保交换的密钥是安全的前提下进行对称加密方式通信。

数字签名—解决报文可能遭遇篡改的问题
作用:

  1. 确定消息是由发送方签名并发出。
  2. 确定消息的完整性,证明数据是否未被篡改

数字签名生成过程:

  1. 通过hash函数将文本信息生成信息摘要,然后通过发送者的私钥加密生成数字签名,与原文一起传送给接收者
  2. 接收者使用发送者的公钥解密被加密的摘要信息,然后使用hash()函数处理当前摘要信息,与上一步中的摘要信息进行对比以此来检验信息的完整性

数字证书—解决通信方身份可能被伪装的问题
数字证书认证机构处于客户端与服务器双方都可信赖的第三方机构的立场上

在这里插入图片描述

  1. 客户端向服务端发送请求
  2. 服务端返回公钥证书
  3. 客户端验证证书的有效性,验证成功后,利用伪随机生成会话秘钥,使用公钥加密会话秘钥
  4. 服务端接收到公钥加密的会话秘钥,通过私钥进行解密,再通过会话秘钥将明文数据内容加密发送给客户端
  5. 客户端通过会话秘钥解密,同样使用会话秘钥加密明文数据内容发送给客户端
  6. 也就是在明文数据发送前使用非对称加密的方式进行密钥传递,在数据传输阶段使用对称加密的方式

为何部所有的网站都是用HTTPS
7. ssl证书的办法有门槛
8. HTTPS在传输过程中因为有多次的加密和解密,所以消耗和传输效率要低于HTTP

12. 遍历器interater

https://es6.ruanyifeng.com/#docs/iterator

13. webpack plugin和loader

手写 plugin和loader

webpack只能理解JS和JSON文件,loader能够让webpack有能力处理其他类型的文件

loader:
https://blog.csdn.net/weixin_43294560/article/details/112963807
loader

  1. loader主要通过两个属性来和webpack进行联动
test:识别哪些文件会被转换
use:定义在转换时应该使用哪些loader

在这里插入图片描述
2. 上手
一个loader本质上就是一个导出的模块,这个函数只有一个参数,就是包含资源文件内容的字符串

// content是唯一的参数,包含资源文件内容的字符串
module.exports = function(content) {
	
}

// 四种loader:同步loader,异步loader,Raw loader和Pith loader

同步loader(一般转换都是同步loader)
function(content) {
	// 对 content 进行一些处理
    const res = dosth(content)
	// 可以直接return
	return content;

	// 也可以使用this.callback()来告诉webpack在使用这个方法来处理,只需要return就行
	// 获取到用户传给当前 loader 的参数
	const options = this.getOptions()
	const res = someSyncOperation(content, options)
	this.callback(null, res, sourceMaps);
	// 注意这里由于使用了 this.callback 直接 return 就行
	return

}

this.callback(
  err: Error | null, // 一个无法正常编译时的 Error 或者 直接给个 null
  content: string | Buffer,// 我们处理后返回的内容 可以是 string 或者 Buffer()
  sourceMap?: SourceMap, // 可选 可以是一个被正常解析的 source map
  meta?: any // 可选 可以是任何东西,比如一个公用的 AST 语法树
);


异步loader(比如一些需要网络请求的场景)
module.exports = function (content) {
  var callback = this.async()
  someAsyncOperation(content, function (err, result) {
    if (err) return callback(err)
    callback(null, result, sourceMaps, meta)
  })
}

对于异步 loader 我们主要需要使用 this.async() 来告知 webpack 这次构建操作是异步的

Raw loader
默认情况下,资源会被转为UTF-8格式后再作为参数传给loader,通过设置 raw 为 true,loader 可以接收原始的 Buffer(二进制)。每一个 loader 都可以用 String 或者 Buffer 的形式传递它的处理结果。complier 将会把它们在 loader 之间相互转换。大家熟悉的 file-loader 就是用了这个.简而言之:你加上 module.exports.raw = true; 传给你的就是 Buffer 了,处理返回的类型也并非一定要是 Buffer,webpack 并没有限制。

module.exports = function (content) {
  console.log(content instanceof Buffer); // true
  return doSomeOperation(content)
}
// 划重点↓
module.exports.raw = true;

Pitching loader
我们每一个 loader 都可以有一个 pitch 方法,大家都知道,loader 是按照从右往左的顺序被调用的,但是实际上,在此之前会有一个按照从左往右执行每一个 loader 的 pitch 方法的过程。 pitch 方法共有三个参数:
remainingRequest:loader 链中排在自己后面的 loader 以及资源文件的绝对路径以!作为连接符组成的字符串。
precedingRequest:loader 链中排在自己前面的 loader 的绝对路径以!作为连接符组成的字符串。
data:每个 loader 中存放在上下文中的固定字段,可用于 pitch 给 loader 传递数据。

在 pitch 中传给 data 的数据,在后续的调用执行阶段,是可以在 this.data 中获取到的:
// a-loader.js
module.exports = function (content) {
  return someSyncOperation(content, this.data.value);// 这里的 this.data.value === 42
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  data.value = 42;
};

注意! 如果某一个 loader 的 pitch 方法中返回了值,那么他会直接“往回走”,跳过后续的步骤,来举个例子
在这里插入图片描述
如果在其中一个loader的pitch方法中通过return传递了返回值,那么就不会再继续往前执行下一个loader的pitch方法了,而是会从当前的pitch方法返回执行

// b-loader.js
module.exports = function (content) {
  return someSyncOperation(content);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  return "诶,我直接返回,就是玩儿~"
};

在这里插入图片描述
Plugin
一个最基本的 plugin 需要包含这些部分:

一个 JavaScript 类
一个 apply 方法,apply 方法在 webpack 装载这个插件的时候被调用,并且会传入 compiler 对象。
使用不同的 hooks 来指定自己需要发生的处理行为
在异步调用时最后需要调用 webpack 提供给我们的 callback 或者通过 Promise 的方式(后续异步编译部分会详细说)

class HelloPlugin{
  apply(compiler){
    compiler.hooks.<hookName>.tap(PluginName,(params)=>{
      /** do some thing */
    })
  }
}
module.exports = HelloPlugin

compiler 对象可以理解为一个和 webpack 环境整体绑定的一个对象,它包含了所有的环境配置,包括 options,loader 和 plugin,当 webpack 启动时,这个对象会被实例化,并且他是全局唯一的,上面我们说到的 apply 方法传入的参数就是它。
compilation 在每次构建资源的过程中都会被创建出来,一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。它同样也提供了很多的 hook 。

同步
class HelloPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(HelloPlugin, (compilation, callback) => {
      setTimeout(() => {
        console.log('async')
        callback()
      }, 1000)
    })
  }
}
module.exports = HelloPlugin

异步
class HelloPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapPromise(HelloPlugin, (compilation) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          console.log('async')
          resolve()
        }, 1000)
      })
    })
  }
}
module.exports = HelloPlugin

使用compilation提供的hooks
class OutLogPlugin {
  constructor(options) {
    this.outFileName = options.outFileName
  }
  apply(compiler) {
    // 可以从编译器对象访问 webpack 模块实例
    // 并且可以保证 webpack 版本正确
    const { webpack } = compiler
    // 获取 Compilation 后续会用到 Compilation 提供的 stage
    const { Compilation } = webpack
    const { RawSource } = webpack.sources
    /** compiler.hooks.<hoonkName>.tap/tapAsync/tapPromise */
    compiler.hooks.compilation.tap('OutLogPlugin', (compilation) => {
      compilation.hooks.processAssets.tap(
        {
          name: 'OutLogPlugin',
          // 选择适当的 stage,具体参见:
          // https://webpack.js.org/api/compilation-hooks/#list-of-asset-processing-stages
          stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
        },
        (assets) => {
          let resOutput = `buildTime: ${new Date().toLocaleString()}\n\n`
          resOutput += `| fileName  | fileSize  |\n| --------- | --------- |\n`
          Object.entries(assets).forEach(([pathname, source]) => {
            resOutput += `| ${pathname} | ${source.size()} bytes |\n`
          })
          compilation.emitAsset(
            `${this.outFileName}.md`,
            new RawSource(resOutput),
          )
        },
      )
    })
  }
}
module.exports = OutLogPlugin
14. 强引用和弱引用

强引用和弱引用

强引用:将一个引用对象通过变量或常量保存时,那么这个变量或常量就是强引用,这个对象就不会被回收。

弱引用:即垃圾回收机制并不认可这种引用之间的联系,当其对应的强引用关系都被解除之后,垃圾回收机制会忽视掉弱引用而直接回收该变量

在这里插入图片描述

15. 盒子模型

w3c标准盒子模型:
在这里插入图片描述
width: 只包含content
box-sizing: content-box;

IE怪异盒子模型
在这里插入图片描述
width: 包括content,padding和border
box-sizing: border-box

16. 最长连续递增子序列
function maxLength(nums) {
	if (nums.length === 0) return 0;
	let count = 1, maxLength = 0;
	for (let i = 1; i< nums.length; i++) {
		if (nums[i] < nums[i + 1]) {
			count++
		} else {
			count = 1;
		}
		maxLength = Math.max(maxLength, count);
	}

	return maxLength;
}
17. 最长严格递增子序列

https://leetcode.cn/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-dong-tai-gui-hua-2/

function maxLength(nums){
	if (nums.length === 0) return 0;
	let dp = new Array(nums.length).fill(1);
	let maxLength = 0;
	for (let i = 0; i<nums.length; i++){
		for (let j=0; j<i; j++) {
			if (nums[j] < nums[i]) {
				dp[i] = Math.max(dp[i], dp[j] + 1);
			}
		}
		maxLength = Math.max(maxLength, dp[i]);
	}
	return maxLength;
}
18. 浏览器缓存

强缓存:不需要发送HTTP请求,读取浏览器的缓存。
http/1.0:Expires

Expires: Wed, 22 Nov 2019 08:41:00 GMT

存在的问题:服务器时间和浏览器时间可能并不一致,所以读取的过期时间可能不准确。
http/1.1:Cache-Control

Cache-Control:max-age=3600

不仅仅只有max-age这一个属性。
public:客户端和代理服务器都可以缓存
private:只有浏览器能缓存
no-store:不进行任何形式的缓存

(Expires和Cache-Control同时存在时,优先考虑Cache-Control)

协商缓存:需要发送HTTP请求,并且在请求头中携带相应的缓存标识,由服务器根据缓存标识决定是否使用缓存。

Last-Modified:即最后修改时间
在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段,浏览器接收后,如果再次请求,会在请求头中携带If-Modified-Since字段,这个值也就是服务器传来的最后修改时间。
返回304,告诉浏览器直接从缓存获取资源

ETag:根据服务器当前的文件内容,给文件生成的唯一标识,服务器会在响应头中加上这个字段,浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,放到请求头中,发给服务器。
返回304,告诉浏览器直接从缓存获取资源

两者对比:
精确度上:ETag优于Lash-Modified
性能上:Last-Modified优于ETag
(两种同时使用,优先考虑ETag)

缓存位置
https://mp.weixin.qq.com/s/mqX-eCfGEY9DveG9-3BPiQ

Service Worker
Service Worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本。.它是异步运行,所以不会对我们的js主线程造成阻塞,我们可以用它来实现消息推送,离线缓存和网络代理
由于是脱离JS主线程,所以并不能够操作DOM,但是依旧可以用它来存储一些数据和逻辑
(HTML5中的缓存机制就是通过定义了一份mainfest文件清单来实现的)

Memory Cache:内存缓存
效率上来讲是最快的,但是存活时间也是最短的,渲染进程结束后,内存缓存消失。

Disk Cache:磁盘中的缓存
存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。

Push Cache:
https://blog.csdn.net/MOEIAW/article/details/121004994
推送缓存,浏览器缓存的最后一道防线,是HTTP/2的内容。
是在设置了Last-Modifed但没有设置Cache-Control或者Expires时触发,也就是只拿到最后更新时间,但没有设置过期时间,这种情况下浏览器会有一个默认的缓存策略push cache,自动设置过期时间:(Date - Last-Modified)*0.1,也就是当前时间减去最后更新时间后再乘10%。

19. 跨域处理

同源策略: 指”协议+域名+端口“三者相等
在这里插入图片描述
但有三个标签允许跨域加载资源

请求跨域的情况下,请求实际上已经发送出去了,并且服务器也会正常返回结果,只不过被浏览器拦截了

JSONP:
利用

将方法名拼接到url后面,服务器接收到字符串之后通过解析然后再res中返回该方法并且传递参数

// index.html
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script')
    window[callback] = function(data) {
      resolve(data)
      document.body.removeChild(script)
    }
    params = { ...params, callback } // wd=b&callback=show
    let arrs = []
    for (let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${arrs.join('&')}`
    document.body.appendChild(script)
  })
}
jsonp({
  url: 'http://localhost:3000/say',
  params: { wd: 'Iloveyou' },
  callback: 'show'
}).then(data => {
  console.log(data)
})

上面这段代码相当于向http://localhost:3000/say?wd=Iloveyou&callback=show这个地址请求数据,然后后台返回show('我不爱你'),最后会运行show()这个函数,打印出'我不爱你'

// 服务器端接口
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
  let { wd, callback } = req.query
  console.log(wd) // Iloveyou
  console.log(callback) // show
  res.end(`${callback}('我不爱你')`)
})
app.listen(3000)

Cors:
服务端设置响应头 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。

简单请求:
使用GET,POST,HEAD方法请求
Content-Type的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded

复杂请求:
复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求

预检:
预检示例
对于非简单请求来说,我们在客户端设置发送的请求实际上反馈到服务端是发送了两次请求,首先发送options方法,服务端检测请求头中的Access-Control-Request-Method来识别客户端发送的请求,以此来决定需要返回对应的响应头。浏览器会检验即将发送的请求方法、请求头是否符合服务端需要,如果满足则发送实际的请求,否则会在控制台报一个CORS错误。

// 客户端,发送一个非简单请求示例
var req = new XMLHttpRequest()

req.onreadystatechange = function (e) {
    if (req.readyState === 4 && req.status === 200) {
        console.log('请求成功')
    }
}

req.open('GET', 'http://127.0.0.1:3003/static/pic', true)
req.setRequestHeader('X-Pong', 'ping')
req.send()

// 服务端
router
.get('/', (ctx, next) => {
  ctx.body = 'index'
})
.options('/static/pic', (ctx, next) => {
  let reqMethod = ctx.headers['access-control-request-method']
  let origin = ctx.headers['origin'] || '*'
  // 打印预检请求头
  console.log(util.inspect(ctx.headers))

  if (/get/i.test(reqMethod)) {
    ctx.set({
      // 只支持GET方法
      'Access-Control-Allow-Method': 'GET',
      // 支持额外的X-Pong头部字段
      'Access-Control-Allow-Headers': 'X-Pong',
      'Access-Control-Allow-Origin': origin,
      // 预检有效缓存10分钟
      'Access-Control-Max-Age': '600'
    })
    ctx.status = 204
  } else {
    ctx.status = 405
    ctx.body = '<p>This resource can only be preflight by GET.</p>'
  }
})
.get('/static/pic', (ctx, next) => {
  // 这是正常的跨域CORS发送文件
  let origin = ctx.headers['origin'] || '*'
  let filePath = path.resolve(__dirname, '../mime/pic.jpg')
  ctx.set('Access-Control-Allow-Origin', origin)
  ctx.type = path.extname(filePath)
  ctx.body = fs.createReadStream(filePath)
})

app.use(router.routes())
app.use(router.allowedMethods())

app.listen(3003, function () {
  console.log('server running on port 3003...')
})


19. webpack如何做性能优化(看到这了)
  1. 打包检测工具
    体积分析:webpack-bundle-analyzer
    速度分析:speed-measure-webpack-plugin
20. es module 和 commonjs的区别

CommonJS:
提供了四个重要的环境变量为模块化的实现提供支持
module、exports、require、global
require第一次之后,以后的每一次require都是从require.cache中寻找的。所以你同一个文件require进来的是同一个对象(引用的是同一个对象)。所以才会有你说的那样

ES Module(ES6):
import、export:通过export导出的模块,在使用import引用时需要大括号
export default:不需要大括号

ES Module 和 CommonJS的差异

  1. CommonJS模块输出的是值的拷贝(浅拷贝),也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。而ES Module输出的是值的引用,也就是说,在遇到import加载命令时,只会生成一个只读引用,等到脚本真正执行时,再根据这个只读引用到被加载的模块里面取值。
  2. CommonJS是运行时加载,在输入时先加载整个模块,生成一个对象后,再从对象上读取方法。ES Module是编译时输出接口,即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
  3. CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
21. react里如何做动态加载
  1. React.lazy(async () => import(‘./module’))
    需要在外层使用<Suspence>来包裹
    Suspense 的 fallback 表示自定义加载的 UI,可以为 null,但不能不存在。
22. 动态加载的原理是啥,就是 webpack 编译出来的代码(没看的内容)

https://juejin.cn/post/6844903888319954952

23. 大数问题

https://www.jianshu.com/p/8c9b68e7e09b
基本思路:
将两个数转化为字符串数组,然后从低位开始进行加减,并且考虑进位问题

24. 写一个处理加法可能产生精度的函数,比如 0.1 + 0.2 = 0.3
26. webpack的编译过程

webpack编译过程

Compiler 对象:负责文件监听和启动编译。Compiler 实例中包含了完整的 webpack 配置,全局只有一个 Compiler 实例。
Compilation 对象:当 webpack 以开发模式运行时,每当检测到文件变化,一次新的 Compilation 将被创建。一个 Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation 对象也提供了很多事件回调供插件做扩展。

初始化:启动构建,读取与合并参数配置,加载Plugin,实例化Compiler对象。

  1. 合并shell和webpack里面配置的所有参数,并且实例化compiler对象
  2. 依次调用plugins里面的插件的apply方法,并且传递compiler对象作为方法的参数使得插件可以调用webpack提供的API
  3. 处理入口文件配置,等待后续的对于文件的递归处理
  4. 调用完所有的plugin的apply方法对模块进行处理

编译:从Entry入口触发,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。(多个loader 的调用顺序是: 从后往前调用)

  1. 遍历loader处理对应的模块,在loader处理完之后,会使用acron(javascript解析器)解析转换之后的内容,生成对应的AST方便后续webpack后续的处理。
    (这里主要最重要的就是compilation过程,compilation 实际上就是调用相应的 loader 处理文件生成 chunks并对这些 chunks 做优化的过程。)
    (在loader中是通过acorn这个JS解析器来实现由代码到ast的转换)

输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统
在这里插入图片描述

27. 进程和线程

区别:

  1. 进程是操作系统资源分配的基本单位,线程是处理器任务调度和执行的基本单位。
  2. 进程之间的地址空间和资源是相互独立的,多个线程之间可以共享同一个进程的资源和地址空间。
  3. 进程之间不会相互影响,而统一进程中如果有线程出现问题,则会堵塞该进程中的其他线程。
  4. 统一进程中的线程可以自由切换,不同进程中的线程切换需要先切换进程,然后再切换线程,且地址空间等都会跟随进程改变
28. http 1.0,1.1,2.0

https://juejin.cn/post/6963931777962344455
第 017

1.0:无状态无连接的应用层协议
无状态:服务器不会追踪和记录请求过的状态
无连接:服务器处理完后立即断开TCP连接
无连接的特性造成的缺陷:
链接无法被复用
队头阻塞问题:由于HTTP/1.0规定下一个请求必须在前一个请求响应到达之前才能发送。假设一个请求响应一直不到达,那么下一个请求就不发送,就到导致阻塞后面的请求。

1.1:
增加Connection字段:可以设置keep-alive来保持长连接
支持并发连接:一个域名可以支持最多6个长连接,同时也可以在这个基础上划分多级域名来增加更多的并行长连接请求

2.0:
不同于1.x使用文本格式传输数据,2.0采用的是二进制格式来传输
多路复用:同一个域名下只需要占用一个TCP连接,而单个连接上可以并行执行请求和响应,大大改善了1.1时域名下并行TCP连接产生的延迟和内存消耗
头部压缩:在HTTP/1.x中,头部元数据都是以纯文本的形式发送的,通常会给每个请求增加500~800字节的负荷。HTTP/2.0使用encoder来减少需要传输的header大小
设置请求优先级

HTTP/1.x keep-alive 与 HTTP/2 多路复用区别:
HTTP/1.x 是基于文本的,只能整体去传;HTTP/2 是基于二进制流的,可以分解为独立的帧,交错发送
HTTP/1.x keep-alive 必须按照请求发送的顺序返回响应;HTTP/2 多路复用不按序响应
HTTP/1.x keep-alive 为了解决队头阻塞,将同一个页面的资源分散到不同域名下,开启了多个 TCP 连接;HTTP/2 同域名下所有通信都在单个连接上完成
HTTP/1.x keep-alive 单个 TCP 连接在同一时刻只能处理一个请求(两个请求的生命周期不能重叠);HTTP/2 单个 TCP 同一时刻可以发送多个请求和响应

29. http状态码

2xx:表示成功状态
204:No Content 表示响应成功但是没有返回数据
206:Partial Content 表示只返回了部分数据(常用于分块下载和断点续传),同时也会有Content-Range的响应头字段

3xx:资源重定向,表示资源位置发生变动,需要重新请求
304:Not Modified 协商缓存时,服务器告诉浏览器可以使用缓存时的状态码

4xx:请求报文出错

5xx:服务器返回出错

30. Accept字段

006

31. undefined 和 null 的区别

undefined
这个变量从根本上就没有定义
隐藏式 空值

null
这个值虽然定义了,但它并未指向任何内存中的对象
声明式 空值

  1. null 转化为 number 时,会转换成 0
    undefined 转换为 number 时,会转换为 NaN

undefined + 1 = NaN
null + 1 = 1

null == undefined // true
很多文章说:undefined 的布尔值是 false , null 的布尔值也是 false ,所以它们在比较时都转化为了 false ,所以 undefined == null 。
实际上并不是这样的。
ECMA 在 11.9.3 章节中明确告诉我们:

If x is null and y is undefined, return true.
If x is undefined and y is null, return true.
这是 JavaScript 底层的内容了,至于更深入的内容,如果有兴趣可以扒一扒 JavaScript 的源码。

null === undefined // false

32. ==强制转换

如果是对象,则首先调用对象的valueOf()作为返回值,如果转换的结果是NaN,则调用toString()方法转换为字符串然后再进行==的转换

33. 作用域

https://juejin.cn/post/6844904165672484871#heading-6
词法作用域(Lexical Scopes)是 javascript 中使用的作用域类型,词法作用域 也可以被叫做 静态作用域。

词法作用域,就意味着函数被定义的时候,它的作用域就已经确定了,和拿到哪里执行没有关系,因此词法作用域也被称为 “静态作用域”。

var value = 1;

function foo() {
  console.log(value);
}

function bar() {
  var value = 2;
  foo();
}

bar(); // 1

块级作用域:
通过{}就可以创建出来一个块级作用域,但是对于JS本身而言,在ES6出来之前,并不是原生就会支持块级作用域的。

// 举个例子
可以看到,我们在块级作用域中定义的a泄露到了全局作用域中
if (true) {
	var a = 1;
}

console.log(a); //1

for (var i = 1; i<5;i++) {
	console.log(i)
}

console.log(i + 'this'); // '5this'

创建作用域的几种方法

1. 函数作用域
function test(){
	var a = 1;
}

2. letconst创建块级作用域
for (let i = 0; i < 5; i++) {
  console.log(i);
}

3. try-catch创建块级作用域
但是在try中使用var创建的变量依旧会被泄漏到全局
try {
	var a = 1;
} catch(err) {
	console.log(err); //err只能在catch这个块级作用域中访问
}

console.log(a); // 1

34. 闭包

https://juejin.cn/post/6844904165672484871

function foo() {
  var a = 2;

  function bar() {
    console.log( a );
  }

  return bar;
}

var baz = foo();

baz(); // 这就形成了一个闭包

闭包的应用场景

1. 单例模式
function Singleton(){
  this.data = 'singleton';
}

Singleton.getInstance = function() {
	var instance;
	
	return function() {
		if (instance) {
			return instance;
		} else {
			instance = new Singleton();
			return instance
		}
	}
}

var sa = Singleton.getInstance();
var sb = Singleton.getInstance();

2. 模拟私有属性
function getGeneratorFunc () {
  var _name = 'John';
  var _age = 22;
    
  return function () {
    return {
      getName: function () {return _name;},
      getAge: function() {return _age;}
    };
  };
}

var p1 = getGeneratorFunc()();
p1.getName(); // 'John';
p1._age; //undefiend;

3. 函数柯里化
35. 内存泄漏

https://juejin.cn/post/6844904165672484871
内存泄漏指当一块内存不再被应用程序所使用后,没能被垃圾回收机制回收而一直存在于内存空间当中,占用了不必要的内存

造成内存泄漏的原因
全局变量的无意创建
function Test() {
	a = 'test';
	console.log(a)
}

Test(); // 'test';
console.log(a); // 'test'

内存泄漏的排查手段
浏览器控制台的memeory,点击左上角的圆圈生成当前时刻的快照信息,里面记录了所有变量的信息内容
在这里插入图片描述
在这里插入图片描述
Constructor — 占用内存的资源类型
Distance — 当前对象到根的引用层级距离
Shallow Size — 对象所占内存(不包含内部引用的其它对象所占的内存)(单位:字节)
Retained Size — 对象所占总内存(包含内部引用的其它对象所占的内存)(单位:字节)

当生成了两次快照以上时,可以选择comparsion来进行更详细的对比,这边需要特别注意这个 #Delta ,如果是正值,就代表新生成的内存多,释放的内存少。其中的闭包项,如果是正值,就说明存在内存泄漏。
在这里插入图片描述

36. 闭包的循环引用问题

类似于解决for循环中的i变量方式一样
可以使用传参或者立即执行函数的方式来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值