为了账号安全,请及时绑定邮箱和手机立即绑定

『中高级前端面试』之终极知识点

2020.05.23 17:21 6369浏览
  • 作者:陈大鱼头

Chrome浏览器进程

在资源不足的设备上,将服务合并到浏览器进程中

浏览器主进程

  • 负责浏览器界面显示

  • 各个页面的管理,创建以及销毁

  • 将渲染进程的结果绘制到用户界面上

  • 网络资源管理

GPU进程

  • 用于3D渲染绘制

网络进程

  • 发起网络请求

插件进程

  • 第三方插件处理,运行在沙箱中

渲染进程

  • 页面渲染

  • 脚本执行

  • 事件处理

网络传输流程

生成HTTP请求消息

  1. 输入网址

  2. 浏览浏览器解析URL

  3. 生成HTTP请求信息

  • https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/http-request.png

  • https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/http-response.png

  1. 收到响应

| 状态码 | 含义 |

| ------ | ------------------------ |

| 1xx | 告知请求的处理进度和情况 |

| 2xx | 成功 |

| 3xx | 表示需要进一步操作 |

| 4xx | 客户端错误 |

| 5xx | 服务端错误 |

向DNS服务器查询Web服务器的IP地址

  1. Socket库提供查询IP地址的功能

  2. 通过解析器向DNS服务器发出查询

全世界DNS服务器的大接力

  1. 寻找相应的DNS服务器并获取IP地址

  2. 通过缓存加快DNS服务器的响应

委托协议栈发送消息

协议栈通过TCP协议收发数据的操作。

  1. 创建套接字

https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/create.png

  • 浏览器,邮件等一般的应用程序收发数据时用TCP

  • DNS查询等收发较短的控制数据时用UDP

  1. 连接服务器

浏览器调用Socket.connect

  • 在TCP模块处创建表示连接控制信息的头部

  • 通过TCP头部中的发送方和接收方端口号找到要连接的套接字

https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/connect.png

  1. 收发数据

浏览器调用Socket.write

  • 将HTTP请求消息交给协议栈

  • 对较大的数据进行拆分,拆分的每一块数据加上TCP头,由IP模块来发送

  • 使用ACK号确认网络包已收到

  • 根据网络包平均往返时间调整ACK号等待时间

  • 使用窗口有效管理ACK号

https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/ack_window.png

  • ACK与窗口的合并

  • 接收HTTP响应消息

  1. 断开管道并删除套接字
  • 数据发送完毕后断开连接

https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/disconnect.png

  • 删除套接字
  1. 客户端发送FIN

  2. 服务端返回ACK号

  3. 服务端发送FIN

  4. 客户端返回ACK号

https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/Socket.png

https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/whole.png

网络协议

https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/js/%20interview2/osl.png

TCP

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。

  • 基于流的方式

  • 面向连接

  • 丢包重传

  • 保证数据顺序

UDP

Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768 描述了 UDP。

  • UDP是非连接的协议,也就是不会跟终端建立连接

  • UDP包信息只有8个字节

  • UDP是面向报文的。既不拆分,也不合并,而是保留这些报文的边界

  • UDP可能丢包

  • UDP不保证数据顺序

HTTP

  • HTTP/0.9:GET,无状态的特点形成

  • HTTP/1.0:支持POST,HEAD,添加了请求头和响应头,支持任何格式的文件发送,添加了状态码、多字符集支持、多部分发送、权限、缓存、内容编码等

  • HTTP/1.1:默认长连接,同时6 个 TCP连接,CDN 域名分片

  • HTTPS:HTTP + TLS(非对称加密对称加密

  1. 客户端发出https请求,请求服务端建立SSL连接

  2. 服务端收到https请求,申请或自制数字证书,得到公钥和服务端私钥,并将公钥发送给客户端

  3. 户端验证公钥,不通过验证则发出警告,通过验证则产生一个随机的客户端私钥

  4. 客户端将公钥与客户端私钥进行对称加密后传给服务端

  5. 服务端收到加密内容后,通过服务端私钥进行非对称解密,得到客户端私钥

  6. 服务端将客户端私钥和内容进行对称加密,并将加密内容发送给客户端

  7. 客户端收到加密内容后,通过客户端私钥进行对称解密,得到内容

  • HTTP/2.0:多路复用(一次TCP连接可以处理多个请求),服务器主动推送,stream传输。

  • HTTP/3:基于 UDP 实现了QUIC 协议

  • 建立好HTTP2连接

  • 发送HTTP2扩展帧

  • 使用QUIC建立连接

  • 如果成功就断开HTTP2连接

  • 升级为HTTP3连接

注:RTT = Round-trip time

页面渲染流程

构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成

  1. 创建DOM tree
  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中。

  • 不可见的节点会被布局树忽略掉。

  1. 样式计算
  • 创建CSSOM tree

  • 转换样式表中的属性值

  • 计算出DOM节点样式

  1. 生成layout tree

  2. 分层

  • 生成图层树(LayerTree)

  • 拥有层叠上下文属性的元素会被提升为单独的一层

  • 需要剪裁(clip)的地方也会被创建为图层

  • 图层绘制

  1. 将图层转换为位图

  2. 合成位图并显示在页面中

页面更新机制

  • 更新了元素的几何属性(重排)

  • 更新元素的绘制属性(重绘)

  • 直接合成

  • CSS3的属性可以直接跳到这一步

JS执行机制

代码提升(为了编译)

  • 变量提升

  • 函数提升(优先级最高)

编译代码

  1. 生成抽象语法树(AST)和执行上下文

  2. 第一阶段是分词(tokenize),又称为词法分析

  3. 第二阶段是解析(parse),又称为语法分析

  4. 生成字节码

字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。

  1. 执行代码

执行代码

  • 执行全局代码时,创建全局上下文

  • 调用函数时,创建函数上下文

  • 使用eval函数时,创建eval上下文

  • 执行局部代码时,创建局部上下文

类型

基本类型

  • Undefined

  • Null

  • Boolean

  • String

  • Symbol

  • Number

  • Object

  • BigInt

复杂类型

  • Object

隐式转换规则

基本情况

  • 转换为布尔值

  • 转换为数字

  • 转换为字符串

转换为原始类型

对象在转换类型的时候,会执行原生方法ToPrimitive

其算法如下:

  1. 如果已经是 原始类型,则返回当前值;

  2. 如果需要转 字符串 则先调用toSting方法,如果此时是 原始类型 则直接返回,否则再调用valueOf方法并返回结果;

  3. 如果不是 字符串,则先调用valueOf方法,如果此时是 原始类型 则直接返回,否则再调用toString方法并返回结果;

  4. 如果都没有 原始类型 返回,则抛出 TypeError类型错误。

当然,我们可以通过重写Symbol.toPrimitive来制定转换规则,此方法在转原始类型时调用优先级最高。


const  data = {

valueOf () {

return  1;

},

toString () {

return  '1';

},

[Symbol.toPrimitive]() {

return  2;

}

};

data + 1  // 3

转换为布尔值

对象转换为布尔值的规则如下表:

| 参数类型 | 结果 |

| --------- | ------------------------------------------------------------ |

| Undefined | 返回 false。 |

| Null | 返回 false。 |

| Boolean | 返回 当前参数。 |

| Number | 如果参数为+0-0NaN,则返回 false;其他情况则返回 true。 |

| String | 如果参数为空字符串,则返回 false;否则返回 true。 |

| Symbol | 返回 true。 |

| Object | 返回 true。 |

转换为数字

对象转换为数字的规则如下表:

| 参数类型 | 结果 |

| --------- | ----------------------------------------------------------- |

| Undefined | 返回 NaN。 |

| Null | Return +0. |

| Boolean | 如果参数为 true,则返回 1false则返回 +0。 |

| Number | 返回当前参数。 |

| String | 先调用 ToPrimitive,再调用 ToNumber,然后返回结果。 |

| Symbol | 抛出 TypeError错误。 |

| Object | 先调用 ToPrimitive,再调用 ToNumber,然后返回结果。 |

转换为字符串

对象转换为字符串的规则如下表:

| 参数类型 | 结果 |

| --------- | ----------------------------------------------------------- |

| Undefined | 返回 "undefined"。 |

| Null | 返回 "null"。 |

| Boolean | 如果参数为 true ,则返回 "true";否则返回 "false"。 |

| Number | 调用 NumberToString,然后返回结果。 |

| String | 返回 当前参数。 |

| Symbol | 抛出 TypeError错误。 |

| Object | 先调用 ToPrimitive,再调用 ToString,然后返回结果。 |

this

this 是和执行上下文绑定的。

执行上下文:

  • 全局执行上下文:全局执行上下文中的 this 也是指向 window 对象。

  • 函数执行上下文:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的。

  • eval 执行上下文:执行eval环境内部的上两个情况。

根据优先级最高的来决定 this 最终指向哪里。

首先,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。

三点注意:

  1. 当函数作为对象的方法调用时,函数中的 this 就是该对象;

  2. 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;

  3. 嵌套函数中的 this 不会继承外层函数的 this 值。

  4. 我们还提了一下箭头函数,因为箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this。

闭包

没有被引用的闭包会被自动回收,但还存在全局变量中,则依然会内存泄漏。

在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。


var  getNum

function  getCounter() {

var  n = 1

var  inner = function() {

n++

}

return  inner

}

getNum = getCounter()

getNum() // 2

getNum() // 3

getNum() // 5

getNum() // 5

作用域

全局作用域

对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。

函数作用域

函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。

局部作用域

使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。

作用域链

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。

原型&原型链

其实每个 JS 对象都有 __proto__ 属性,这个属性指向了原型。

原型也是一个对象,并且这个对象中包含了很多函数,对于 obj 来说,可以通过 __proto__ 找到一个原型对象,在该对象中定义了很多函数让我们来使用。

原型链:

  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它

  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它

  • 函数的 prototype 是一个对象

  • 对象的 __proto__ 属性指向原型, __proto__ 将对象和原型连接起来组成了原型链

V8工作原理

数据存储

  • 栈空间:调用栈,存储执行上下文,以及存储原始类型的数据

  • 堆空间:存储引用类型

原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。

垃圾回收

  • 回收调用栈内的数据:执行上下文结束且没有被引用时,则会通过向下移动 记录当前执行状态的指针(称为 ESP) 来销毁该函数保存在栈中的执行上下文。

  • 回收堆里的数据:

V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。

  • 副垃圾回收器,主要负责新生代的垃圾回收。
  • 主垃圾回收器,主要负责老生代的垃圾回收。

垃圾回收重要术语:

  • 代际假说
  • 分代收集

工作流程:

  1. 标记空间中活动对象和非活动对象

  2. 回收非活动对象所占据的内存

  3. 内存整理

一旦执行垃圾回收算法,会导致 全停顿(Stop-The-World) 。但是V8有 增量标记算法。V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成。

事件循环

微任务(microtask)

  • process.nextTick

  • promise

  • Object.observe (已废弃)

  • MutationObserver

宏任务(macrotask)

  • script

  • setTimeout

  • setInterval

  • setImmediate

  • I/O

  • UI rendering

执行顺序

  1. 执行同步代码,这属于宏任务

  2. 执行栈为空,查询是否有微任务需要执行

  3. 必要的话渲染 UI

  4. 然后开始下一轮 Event loop,执行宏任务中的异步代码

浏览器安全

攻击方式

  • xss:将代码注入到网页

  • 持久型:写入数据库

  • 非持久型:修改用户代码

  • csrf:跨站请求伪造。

  • Get 请求不对数据进行修改

  • 不让第三方网站访问到用户 Cookie

  • 阻止第三方网站请求接口

  • 请求时附带验证信息,比如验证码或者 Token

  • 中间人攻击:中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。

当然防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。

CSP

建立白名单

  • HTTP Header 中的 Content-Security-Policy

  • <meta http-equiv="Content-Security-Policy">

浏览器性能

DNS预解析

  • <link rel="dns-prefetch" href="" />

  • Chrome 和 Firefox 3.5+ 能自动进行预解析

  • 关闭DNS预解析:<meta http-equiv="x-dns-prefetch-control" content="off|on">

https://camo.githubusercontent.com/4e1a2fff1565062e3c71363f91dd1fba6a5e0d8b/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031392f312f352f313638316332316530343535326637373f773d3232313326683d39343826663d706e6726733d333131313733

强缓存

  1. Expires

  • 缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。

  • Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

  1. Cache-Control

https://camo.githubusercontent.com/ba9d2cbebd6daefbf1050a73726cf972e9a4fbc5/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031392f312f332f313638313436376438323562326562653f773d35363226683d33343326663d706e6726733d313031373538

https://camo.githubusercontent.com/91cdfdc6e71e175718bf085f05c7ba40aa0da084/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32302f313633376430626264366461313134663f773d38323026683d37333926663d706e6726733d313033333139

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。

  • 服务器响应头:Last-Modified,Etag

  • 浏览器请求头:If-Modified-Since,If-None-Match

**Last-Modified ** 与 If-Modified-Since 配对。Last-Modified 把Web应用最后修改时间告诉客户端,客户端下次请求之时会把 If-Modified-Since 的值发生给服务器,服务器由此判断是否需要重新发送资源,如果不需要则返回304,如果有则返回200。这对组合的缺点是只能精确到秒,而且是根据本地打开时间来记录的,所以会不准确。

**Etag ** 与 If-None-Match 配对。它们没有使用时间作为判断标准,而是使用了一组特征串。Etag把此特征串发生给客户端,客户端在下次请求之时会把此特征串作为If-None-Match的值发送给服务端,服务器由此判断是否需要重新发送资源,如果不需要则返回304,如果有则返回200。

NodeJs

单线程

基础概念:

  • 进程:进程(英语:process),是指计算机中已运行的程序。进程曾经是分时系统的基本运作单位。

  • 线程:线程(英语:thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。

  • 协程:协程(英语:coroutine)是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。

Node 中最核心的是 v8 引擎,在 Node 启动后,会创建 v8 的实例,这个实例是多线程的,各个线程如下:

  • 主线程:编译、执行代码。

  • 编译/优化线程:在主线程执行的时候,可以优化代码。

  • 分析器线程:记录分析代码运行时间,为 Crankshaft 优化代码执行提供依据。

  • 垃圾回收的几个线程。

非阻塞I/O

阻塞 是指在 Node.js 程序中,其它 JavaScript 语句的执行,必须等待一个非 JavaScript 操作完成。这是因为当 阻塞 发生时,事件循环无法继续运行 JavaScript。

在 Node.js 中,JavaScript 由于执行 CPU 密集型操作,而不是等待一个非 JavaScript 操作(例如 I/O)而表现不佳,通常不被称为 阻塞。在 Node.js 标准库中使用 libuv 的同步方法是最常用的 阻塞 操作。原生模块中也有 阻塞 方法。

事件循环


┌───────────────────────────┐

┌─>│ timers │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ pending callbacks │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ idle, prepare │

│ └─────────────┬─────────────┘ ┌───────────────┐

│ ┌─────────────┴─────────────┐ │ incoming: │

│ │ poll │<─────┤ connections, │

│ └─────────────┬─────────────┘ │ data, etc. │

│ ┌─────────────┴─────────────┐ └───────────────┘

│ │ check │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

└──┤ close callbacks │

└───────────────────────────┘

注意:每个框被称为事件循环机制的一个阶段。

在 Windows 和 Unix/Linux 实现之间存在细微的差异,但这对演示来说并不重要。

阶段概述:

  • 定时器:本阶段执行已经被 setTimeout()setInterval() 的调度回调函数。

  • 待定回调:执行延迟到下一个循环迭代的 I/O 回调。

  • idle, prepare:仅系统内部使用。

  • 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。

  • 检测setImmediate() 回调函数在这里执行。

  • 关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)

在每次运行的事件循环之间,Node.js 检查它是否在等待任何异步 I/O 或计时器,如果没有的话,则完全关闭。

process.nextTick():它是异步 API 的一部分。从技术上讲不是事件循环的一部分。不管事件循环的当前阶段如何,都将在当前操作完成后处理 nextTickQueue。这里的一个操作被视作为一个从底层 C/C++ 处理器开始过渡,并且处理需要执行的 JavaScript 代码。

Libuv

Libuv 是一个跨平台的异步 IO 库,它结合了 UNIX 下的 libev 和 Windows 下的 IOCP 的特性,最早由 Node.js 的作者开发,专门为 Node.js 提供多平台下的异步IO支持。Libuv 本身是由 C++ 语言实现的,Node.js 中的非阻塞 IO 以及事件循环的底层机制都是由 libuv 实现的。

在 Windows 环境下,libuv 直接使用Windows的 IOCP 来实现异步IO。在 非Windows 环境下,libuv使用多线程(线程池Thread Pool)来模拟异步IO,这里仅简要提一下 libuv 中有线程池的概念,之后的文章会介绍 libuv 如何实现进程间通信。

手写代码

new操作符


var  New = function (Fn) {

var  obj = {} // 创建空对象

var  arg = Array.prototype.slice.call(arguments, 1)

obj.__proto__ = Fn.prototype  // 将obj的原型链__proto__指向构造函数的原型prototype

obj.__proto__.constructor = Fn  // 在原型链 __proto__上设置构造函数的构造器constructor,为了实例化Fn

Fn.apply(obj, arg) // 执行Fn,并将构造函数Fn执行obj

return  obj  // 返回结果

}

深拷贝


const  getType = (data) => { // 获取数据类型

const  baseType = Object.prototype.toString.call(data).replace(/^\[object\s(.+)\]$/g, '$1').toLowerCase();

const  type = data  instanceof  Element ? 'element' : baseType;

return  type;

};

const  isPrimitive = (data) => { // 判断是否是基本数据类型

const  primitiveType = 'undefined,null,boolean,string,symbol,number,bigint,map,set,weakmap,weakset'.split(','); // 其实还有很多类型

return  primitiveType.includes(getType(data));

};

const  isObject = data  => (getType(data) === 'object');

const  isArray = data  => (getType(data) === 'array');

const  deepClone = data  => {

let  cache = {}; // 缓存值,防止循环引用

const  baseClone = _data  => {

let  res;

if (isPrimitive(_data)) {

return  data;

} else  if (isObject(_data)) {

res = { ..._data }

} else  if (isArray(_data)) {

res = [..._data]

};

// 判断是否有复杂类型的数据,有就递归

Reflect.ownKeys(res).forEach(key  => {

if (res[key] && getType(res[key]) === 'object') {

// 用cache来记录已经被复制过的引用地址。用来解决循环引用的问题

if (cache[res[key]]) {

res[key] = cache[res[key]];

} else {

cache[res[key]] = res[key];

res[key] = baseClone(res[key]);

};

};

});

return  res;

};

return  baseClone(data);

};

手写bind


Function.prototype.bind2 = function (context) {

if (typeof  this !== 'function') {

throw  new  Error('...');

};

var  that = this;

var  args1 = Array.prototype.slice.call(arguments,1);

var  bindFn = function () {

var  args2 = Array.prototype.slice.call(arguments);

var  that2 = this  instanceof  bindFn ? this : context; // 如果当前函数的this指向的是构造函数中的this 则判定为new 操作。如果this是构造函数bindFn new出来的实例,那么此处的this一定是该实例本身。

return  that.apply(

that2,

args1.concat(args2)

);

}

var  Fn = function () {}; // 连接原型链用Fn

// 原型赋值

Fn.prototype = this.prototype; // bindFn的prototype指向和this的prototype一样,指向同一个原型对象

bindFn.prototype = new  Fn();

return  bindFn;

}

手写函数柯里化


const  curry = fn  => {

if (typeof  fn !== 'function') {

throw  Error('No function provided')

}

return  function  curriedFn(...args){

if (args.length < fn.length) {

return  function () {

return  curriedFn.apply(null, args.concat([].slice.call(arguments)))

}

}

return  fn.apply(null, args)

}

}

手写Promise


// 来源于 https://github.com/bailnl/promise/blob/master/src/promise.js

const  PENDING = 0;

const  FULFILLED = 1;

const  REJECTED = 2;

  

const  isFunction = fn  => (typeof  fn === 'function');

const  isObject = obj  => (obj !== null && typeof  obj === 'object');

const  noop = () => {};

  

const  nextTick = fn  =>  setTimeout(fn, 0);

  

const  resolve = (promise, x) => {

if (promise === x) {

reject(promise, new  TypeError('You cannot resolve a promise with itself'));

} else  if (x && x.constructor === Promise) {

if (x._stauts === PENDING) {

const  handler = statusHandler  =>  value  =>  statusHandler(promise, value) ;

x.then(handler(resolve), handler(reject));

} else  if (x._stauts === FULFILLED) {

fulfill(promise, x._value);

} else  if (x._stauts === REJECTED) {

reject(promise, x._value);

};

} else  if (isFunction(x) || isObject(x)) {

let  isCalled = false;

try {

const  then = x.then;

if (isFunction(then)) {

const  handler = statusHandler  =>  value  => {

if (!isCalled) {

statusHandler(promise, value);

}

isCalled = true;

};

then.call(x, handler(resolve), handler(reject));

} else {

fulfill(promise, x);

};

} catch (e) {

if (!isCalled) {

reject(promise, e);

};

};

} else {

fulfill(promise, x);

};

};

  

const  reject = (promise, reason) => {

if (promise._stauts !== PENDING) {

return;

}

promise._stauts = REJECTED;

promise._value = reason;

invokeCallback(promise);

};

  

const  fulfill = (promise, value) => {

if (promise._stauts !== PENDING) {

return;

};

promise._stauts = FULFILLED;

promise._value = value;

invokeCallback(promise);

};

  

const  invokeCallback = (promise) => {

if (promise._stauts === PENDING) {

return;

};

nextTick(() => {

while (promise._callbacks.length) {

const {

onFulfilled = (value  =>  value),

onRejected = (reason  => { throw  reason }),

thenPromise,

} = promise._callbacks.shift();

let  value;

try {

value = (promise._stauts === FULFILLED ? onFulfilled : onRejected)(promise._value);

} catch (e) {

reject(thenPromise, e);

continue;

}

resolve(thenPromise, value);

};

});

};

  

class  Promise {

static  resolve(value) {

return  new  Promise((resolve, reject) =>  resolve(value))

}

static  reject(reason) {

return  new  Promise((resolve, reject) =>  reject(reason))

}

constructor(resolver) {

if (!(this  instanceof  Promise)) {

throw  new  TypeError(`Class constructor Promise cannot be invoked without 'new'`);

};

  

if (!isFunction(resolver)) {

throw  new  TypeError(`Promise resolver ${resolver} is not a function`);

};

  

this._stauts = PENDING;

this._value = undefined;

this._callbacks = [];

  

try {

resolver(value  =>  resolve(this, value), reason  =>  reject(this, reason));

} catch (e) {

reject(this, e);

};

};

  

then(onFulfilled, onRejected) {

const  thenPromise = new  this.constructor(noop);

this._callbacks = this._callbacks.concat([{

onFulfilled:  isFunction(onFulfilled) ? onFulfilled : void  0,

onRejected:  isFunction(onRejected) ? onRejected : void  0,

thenPromise,

}]);

invokeCallback(this);

return  thenPromise;

};

catch(onRejected) {

return  this.then(void  0, onRejected);

};

};

手写防抖函数


const  debounce = (fn = {}, wait=50, immediate) => {

let  timer;

return  function () {

if (immediate) {

fn.apply(this, arguments)

};

if (timer) {

clearTimeout(timer)

timer = null;

};

timer = setTimeout(()=> {

fn.apply(this,arguments)

}, wait);

};

};

手写节流函数


var  throttle = (fn = {}, wait = 0) => {

let  prev = new  Date();

return  function () {

const  args = arguments;

const  now = new  Date();

if (now - prev > wait) {

fn.apply(this, args);

prev = new  Date();

};

}

}

手写instanceOf


const  instanceOf = (left, right) => {

let  proto = left.__proto__;

let  prototype = right.prototype

while (true) {

if (proto === null) {

return  false;

} else  if (proto === prototype) {

return  true;

};

proto = proto.__proto__;

};

}

其它知识

typeof vs instanceof

instanceof 运算符用来检测 constructor.prototype是否存在于参数 object 的原型链上。

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"

参考资料

点击查看更多内容

本文首次发布于慕课网 ,转载请注明出处,谢谢合作

0人点赞

若觉得本文不错,就分享一下吧!

评论

相关文章推荐

正在加载中
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消