关于前端:滴滴前端面试题

29次阅读

共计 13315 个字符,预计需要花费 34 分钟才能阅读完成。

Unicode、UTF-8、UTF-16、UTF-32 的区别?

(1)Unicode

在说 Unicode 之前须要先理解一下 ASCII 码:ASCII 码(American Standard Code for Information Interchange)称为美国规范信息替换码。

  • 它是基于拉丁字母的一套电脑编码零碎。
  • 它定义了一个用于代表常见字符的字典。
  • 它蕴含了 ”A-Z”(蕴含大小写),数据 ”0-9″ 以及一些常见的符号。
  • 它是专门为英语而设计的,有 128 个编码,对其余语言无能为力

ASCII码能够示意的编码无限,要想示意其余语言的编码,还是要应用 Unicode 来示意,能够说 UnicodeASCII 的超集。

Unicode全称 Unicode Translation Format,又叫做对立码、万国码、繁多码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了对立并且惟一的二进制编码,以满足跨语言、跨平台进行文本转换、解决的要求。

Unicode的实现形式(也就是编码方式)有很多种,常见的是 UTF-8UTF-16UTF-32USC-2

(2)UTF-8

UTF-8是应用最宽泛的 Unicode 编码方式,它是一种可变长的编码方式,能够是 1—4 个字节不等,它能够齐全兼容 ASCII 码的 128 个字符。

留神: UTF-8 是一种编码方式,Unicode是一个字符汇合。

UTF-8的编码规定:

  • 对于 单字节 的符号,字节的第一位为 0,前面的 7 位为这个字符的 Unicode 编码,因而对于英文字母,它的 Unicode 编码和 ACSII 编码一样。
  • 对于 n 字节 的符号,第一个字节的前 n 位都是 1,第 n + 1 位设为 0,前面字节的前两位一律设为 10,剩下的没有提及的二进制位,全副为这个符号的 Unicode 码。

来看一下具体的 Unicode 编号范畴与对应的 UTF-8 二进制格局:

编码范畴(编号对应的十进制数) 二进制格局
0x00—0x7F(0-127) 0xxxxxxx
0x80—0x7FF(128-2047) 110xxxxx 10xxxxxx
0x800—0xFFFF(2048-65535) 1110xxxx 10xxxxxx 10xxxxxx
0x10000—0x10FFFF(65536 以上) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

那该如何通过具体的 Unicode 编码,进行具体的 UTF-8 编码呢?步骤如下:

  • 找到该 Unicode 编码的所在的编号范畴,进而找到与之对应的二进制格局
  • Unicode 编码转换为二进制数(去掉最高位的 0)
  • 将二进制数从右往左一次填入二进制格局的 X 中,如果有 X 未填,就设为 0

来看一个理论的例子:
”字的Unicode 编码是:0x9A6C,整数编号是39532(1)首选确定了该字符在第三个范畴内,它的格局是 1110xxxx 10xxxxxx 10xxxxxx(2)39532 对应的二进制数为1001 1010 0110 1100(3)将二进制数填入 X 中,后果是:11101001 10101001 10101100

(3)UTF-16

1. 立体的概念

在理解 UTF-16 之前,先看一下 立体 的概念:Unicode编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区寄存 65536(216)个字符,这称为一个 立体,目前总共有 17 个立体。

最后面的一个立体称为 根本立体 ,它的码点从0 — 216-1,写成 16 进制就是U+0000 — U+FFFF,那剩下的 16 个立体就是 辅助立体,码点范畴是 U+10000—U+10FFFF

2. UTF-16 概念:

UTF-16也是 Unicode 编码集的一种编码模式,把 Unicode 字符集的形象码位映射为 16 位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位须要 1 个或者 2 个 16 位长的码元来示意,因而 UTF-16 也是用变长字节示意的。

3. UTF-16 编码规定:

  • 编号在 U+0000—U+FFFF 的字符(罕用字符集),间接用两个字节示意。
  • 编号在 U+10000—U+10FFFF 之间的字符,须要用四个字节示意。

4. 编码辨认

那么问题来了,当遇到两个字节时,怎么晓得是把它当做一个字符还是和前面的两个字节一起当做一个字符呢?

UTF-16 编码必定也思考到了这个问题,在根本立体内,从 U+D800 — U+DFFF 是一个空段,也就是说这个区间的码点不对应任何的字符,因而这些空段就能够用来映射辅助立体的字符。

辅助立体共有 220 个字符位,因而示意这些字符至多须要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 — U+DBFF,称为 高位 (H),后 10 位映射在 U+DC00 — U+DFFF,称为 低位(L)。这就相当于,将一个辅助立体的字符拆成了两个根本立体的字符来示意。

因而,当遇到两个字节时,发现它的码点在 U+D800 —U+DBFF之间,就能够晓得,它前面的两个字节的码点应该在 U+DC00 — U+DFFF 之间,这四个字节必须放在一起进行解读。

5. 举例说明

以 “𡠀” 字为例,它的 Unicode 码点为 0x21800,该码点超出了根本立体的范畴,因而须要用四个字节来示意,步骤如下:

  • 首先计算超出局部的后果:0x21800 - 0x10000
  • 将下面的计算结果转为 20 位的二进制数,有余 20 位就在后面补 0,后果为:0001000110 0000000000
  • 将失去的两个 10 位二进制数别离对应到两个区间中
  • U+D800 对应的二进制数为 1101100000000000,将 0001000110 填充在它的后 10 个二进制位,失去 1101100001000110,转成 16 进制数为 0xD846。同理,低位为 0xDC00,所以这个字的UTF-16 编码为 0xD846 0xDC00

(4)UTF-32

UTF-32 就是字符所对应编号的整数二进制模式,每个字符占四个字节,这个是间接进行转换的。该编码方式占用的贮存空间较多,所以应用较少。

比方“”字的 Unicode 编号是:U+9A6C,整数编号是39532,间接转化为二进制:1001 1010 0110 1100,这就是它的 UTF-32 编码。

(5)总结

Unicode、UTF-8、UTF-16、UTF-32 有什么区别?

  • Unicode 是编码字符集(字符集),而 UTF-8UTF-16UTF-32 是字符集编码(编码规定);
  • UTF-16 应用变长码元序列的编码方式,相较于定长码元序列的 UTF-32 算法更简单,甚至比同样是变长码元序列的 UTF-8 也更为简单,因为其引入了独特的 代理对 这样的代理机制;
  • UTF-8须要判断每个字节中的结尾标记信息,所以如果某个字节在传送过程中出错了,就会导致前面的字节也会解析出错;而 UTF-16 不会判断结尾标记,即便错也只会错一个字符,所以容错能力教强;
  • 如果字符内容全副英文或英文与其余文字混合,但英文占绝大部分,那么用 UTF-8 就比 UTF-16 节俭了很多空间;而如果字符内容全副是中文这样相似的字符或者混合字符中中文占绝大多数,那么 UTF-16 就占优势了,能够节俭很多空间;

如何对我的项目中的图片进行优化?

  1. 不必图片。很多时候会应用到很多润饰类图片,其实这类润饰图片齐全能够用 CSS 去代替。
  2. 对于挪动端来说,屏幕宽度就那么点,齐全没有必要去加载原图节约带宽。个别图片都用 CDN 加载,能够计算出适配屏幕的宽度,而后去申请相应裁剪好的图片。
  3. 小图应用 base64 格局
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 抉择正确的图片格式:

    • 对于可能显示 WebP 格局的浏览器尽量应用 WebP 格局。因为 WebP 格局具备更好的图像数据压缩算法,能带来更小的图片体积,而且领有肉眼辨认无差别的图像品质,毛病就是兼容性并不好
    • 小图应用 PNG,其实对于大部分图标这类图片,齐全能够应用 SVG 代替
    • 照片应用 JPEG

函数节流

触发高频事件,且 N 秒内只执行一次。

简略版:应用工夫戳来实现,立刻执行一次,而后每 N 秒执行一次。

function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {func.apply(context, args);
            previous = now;
        }
    }
}

最终版:反对勾销节流;另外通过传入第三个参数,options.leading 来示意是否能够立刻执行一次,opitons.trailing 示意完结调用的时候是否还要执行一次,默认都是 true。
留神设置的时候不能同时将 leading 或 trailing 设置为 false。

function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

    var throttled = function() {var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {if (timeout) {clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {timeout = setTimeout(later, remaining);
        }
    };

    throttled.cancel = function() {clearTimeout(timeout);
        previous = 0;
        timeout = null;
    }
    return throttled;
}

节流的应用就不拿代码举例了,参考防抖的写就行。

ES6 新个性

1.ES6 引入来严格模式
    变量必须申明后在应用
    函数的参数不能有同名属性, 否则报错
    不能应用 with 语句 (说实话我根本没用过)
    不能对只读属性赋值, 否则报错
    不能应用前缀 0 示意八进制数, 否则报错 (说实话我根本没用过)
    不能删除不可删除的数据, 否则报错
    不能删除变量 delete prop, 会报错, 只能删除属性 delete global[prop]
    eval 不会在它的外层作用域引入变量
    eval 和 arguments 不能被从新赋值
    arguments 不会主动反映函数参数的变动
    不能应用 arguments.caller (说实话我根本没用过)
    不能应用 arguments.callee (说实话我根本没用过)
    禁止 this 指向全局对象
    不能应用 fn.caller 和 fn.arguments 获取函数调用的堆栈 (说实话我根本没用过)
    减少了保留字(比方 protected、static 和 interface)2. 对于 let 和 const 新增的变量申明

3. 变量的解构赋值

4. 字符串的扩大
    includes():返回布尔值,示意是否找到了参数字符串。startsWith():返回布尔值,示意参数字符串是否在原字符串的头部。endsWith():返回布尔值,示意参数字符串是否在原字符串的尾部。5. 数值的扩大
    Number.isFinite()用来查看一个数值是否为无限的(finite)。Number.isNaN()用来查看一个值是否为 NaN。6. 函数的扩大
    函数参数指定默认值
7. 数组的扩大
    扩大运算符
8. 对象的扩大
    对象的解构
9. 新增 symbol 数据类型

10.Set 和 Map 数据结构 
    ES6 提供了新的数据结构 Set。它相似于数组,然而成员的值都是惟一的,没有反复的值。Set 自身是一个构造函数,用来生成 Set 数据结构。Map 它相似于对象,也是键值对的汇合,然而“键”的范畴不限于字符串,各种类型的值(包含对象)都能够当作键。11.Proxy
    Proxy 能够了解成,在指标对象之前架设一层“拦挡”,外界对该对象的拜访
    都必须先通过这层拦挡,因而提供了一种机制,能够对外界的拜访进行过滤和改写。Proxy 这个词的原意是代理,用在这里示意由它来“代理”某些操作,能够译为“代理器”。Vue3.0 应用了 proxy
12.Promise
    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。特点是:对象的状态不受外界影响。一旦状态扭转,就不会再变,任何时候都能够失去这个后果。13.async 函数 
    async 函数对 Generator 函数的区别:(1)内置执行器。Generator 函数的执行必须靠执行器,而 async 函数自带执行器。也就是说,async 函数的执行,与一般函数截然不同,只有一行。(2)更好的语义。async 和 await,比起星号和 yield,语义更分明了。async 示意函数里有异步操作,await 示意紧跟在前面的表达式须要期待后果。(3)失常状况下,await 命令前面是一个 Promise 对象。如果不是,会被转成一个立刻 resolve 的 Promise 对象。(4)返回值是 Promise。async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象不便多了。你能够用 then 办法指定下一步的操作。14.Class 
    class 跟 let、const 一样:不存在变量晋升、不能反复申明...
    ES6 的 class 能够看作只是一个语法糖,它的绝大部分性能
    ES5 都能够做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。15.Module
    ES6 的模块主动采纳严格模式,不论你有没有在模块头部加上 "use strict";。import 和 export 命令以及 export 和 export default 的区别

说一下购物车的逻辑?

//vue 中购物车逻辑的实现
1. 购物车信息用一个数组来存储,数组中保留对象,对象中有 id 和 count 属性

2. 在 vuex 中 state 中增加一个数据 cartList 用来保留这个数组

3. 因为商品详情页须要用到退出购物车性能,所以咱们须要提供一个 mutation, 用来将购物车信息退出 cartList 中

4. 退出购物车信息的时候,遵循如下规定:如果购物车中曾经有了该商品信息,则数量累加,如果没有该商品信息,则新增一个对象

5. 在商品详情页,点击退出购物车按钮的时候,调用 vuex 提供的 addToCart 这个 mutation 将以后的商品信息(id count)传给 addTocart  this.$store.commit("addToCart", {id:  , count:})

// js 中购物车逻辑的实现
1. 商品页点击“退出购物车”按钮,触发事件

2. 事件调用购物车“减少商品”的 Js 程序(函数、对象办法)3. 向 Js 程序传递传递“商品 id”、“商品数量”等数据

4. 存储“商品 id”、“商品数量”到浏览器的 localStorage 中

** 展现购物车中的商品 ******

1. 关上购物车页面

2. 从 localStorage 中取出“商品 Id”、“商品数量”等信息。3. 调用服务器端“取得商品详情”的接口失去购物车中的商品信息(参数为商品 Id)4. 将取得的商品信息显示在购物车页面。** 实现购物车中商品的购买 ******

1. 用户对购物车中的商品实现购买流程,产生购物订单

2. 革除 localStorage 中存储的曾经购买的商品信息

备注 1:购物车中商品存储的数据除了“商品 id”、“商品数量”之外,依据产品要求还能够有其余的信息,例如残缺的商品详情(这样就不必掉服务器接口取得详情了)、购物车商品的过期工夫,超过工夫的购物车商品在下次关上网站或者购物车页面时被革除。备注 2:购物车商品除了存储在 localStorage 中,依据产品的需要不同,也能够存储在 sessionStorage、cookie、session 中,或者间接向服务器接口发动申请存储在服务器上。何种状况应用哪种形式存储、有啥区别请本人剖析。

说一下你对盒模型的了解?

CSS3 中的盒模型有以下两种: 规范盒模型、IE 盒模型
盒模型都是由四个局部组成的, 别离是 margin、border、padding 和 content
规范盒模型和 IE 盒模型的区别在于设置 width 和 height 时, 所对应的范畴不同
1、规范盒模型的 width 和 height 属性的范畴只蕴含了 content
2、IE 盒模型的 width 和 height 属性的范畴蕴含了 border、padding 和 content
能够通过批改元素的 box-sizing 属性来扭转元素的盒模型;1、box-sizing:content-box 示意规范盒模型(默认值)2、box-sizing:border-box 示意 IE 盒模型(怪异盒模型)

懒加载的概念

懒加载也叫做提早加载、按需加载,指的是在长网页中提早加载图片数据,是一种较好的网页性能优化的形式。在比拟长的网页或利用中,如果图片很多,所有的图片都被加载进去,而用户只能看到可视窗口的那一部分图片数据,这样就节约了性能。

如果应用图片的懒加载就能够解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,缩小了服务器的负载。懒加载实用于图片较多,页面列表较长(长列表)的场景中。

数组扁平化

数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。应用 Array.prototype.flat 能够间接将多层数组拍平成一层:

[1, [2, [3]]].flat(2)  // [1, 2, 3]

当初就是要实现 flat 这种成果。

ES5 实现:递归。

function flatten(arr) {var result = [];
    for (var i = 0, len = arr.length; i < len; i++) {if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]))
        } else {result.push(arr[i])
        }
    }
    return result;
}

ES6 实现:

function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);
    }
    return arr;
}

分片思维解决大数据量渲染问题

题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

实现代码如下:

let ul = document.getElementById("container");
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
// 总页数
let page = total / once;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {if (curTotal <= 0) {return false;}
  // 每页多少条
  let pageCount = Math.min(curTotal, once);
  window.requestAnimationFrame(function () {for (let i = 0; i < pageCount; i++) {let li = document.createElement("li");
      li.innerText = curIndex + i + ":" + ~~(Math.random() * total);
      ul.appendChild(li);
    }
    loop(curTotal - pageCount, curIndex + pageCount);
  });
}
loop(total, index);

扩大思考:对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了 大家自行百度哈 虚构列表和虚构表格在日常我的项目应用还是很频繁的

图片懒加载

实现getBoundClientRect 的实现形式,监听 scroll 事件(倡议给监听事件增加节流),图片加载完会从 img 标签组成的 DOM 列表中删除,最初所有的图片加载结束后须要解绑监听事件。

// scr 加载默认图片,data-src 保留施行懒加载后的图片
// <img src="./default.jpg" data-src="https://xxx.jpg" alt="" />
let imgs = [...document.querySelectorAll("img")];
const len = imgs.length;

let lazyLoad = function() {
    let count = 0;
    let deleteImgs = [];
    // 获取以后可视区的高度
    let viewHeight = document.documentElement.clientHeight;
    // 获取以后滚动条的地位(间隔顶部的间隔, 等价于 document.documentElement.scrollTop)
    let scrollTop = window.pageYOffset;
    imgs.forEach((img) => {
        // 获取元素的大小,及其绝对于视口的地位,如 bottom 为元素底部到网页顶部的间隔
        let bound = img.getBoundingClientRect();
        // 以后图片间隔网页顶部的间隔
        // let imgOffsetTop = img.offsetTop;

        // 判断图片是否在可视区内,如果在就加载(两种判断形式)// if(imgOffsetTop < scrollTop + viewHeight) 
        if (bound.top < viewHeight) {
            img.src = img.dataset.src;  // 替换待加载的图片 src
            count++;
            deleteImgs.push(img);
            // 最初所有的图片加载结束后须要解绑监听事件
            if(count === len) {document.removeEventListener("scroll", imgThrottle);
            }
        }
    });
    // 图片加载完会从 `img` 标签组成的 DOM 列表中删除
    imgs = imgs.filter((img) => !deleteImgs.includes(img));
}

window.onload = function () {lazyLoad();
};
// 应用 防抖 / 节流 优化一下滚动事件
let imgThrottle = debounce(lazyLoad, 1000);
// 监听 `scroll` 事件
window.addEventListener("scroll", imgThrottle);

原型批改、重写

function Person(name) {this.name = name}
// 批改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {getName: function() {}}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // false

能够看到批改原型的时候 p 的构造函数不是指向 Person 了,因为间接给 Person 的原型对象间接用对象赋值时,它的构造函数指向的了根构造函数 Object,所以这时候p.constructor === Object,而不是p.constructor === Person。要想成立,就要用 constructor 指回来:

Person.prototype = {getName: function() {}}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // true

对浏览器内核的了解

浏览器内核次要分成两局部:

  • 渲染引擎的职责就是渲染,即在浏览器窗口中显示所申请的内容。默认状况下,渲染引擎能够显示 html、xml 文档及图片,它也能够借助插件显示其余类型数据,例如应用 PDF 阅读器插件,能够显示 PDF 格局。
  • JS 引擎:解析和执行 javascript 来实现网页的动态效果。

最开始渲染引擎和 JS 引擎并没有辨别的很明确,起初 JS 引擎越来越独立,内核就偏向于只指渲染引擎。

对节流与防抖的了解

  • 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。
  • 函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。

防抖函数的利用场景:

  • 按钮提交场景:防⽌屡次提交按钮,只执⾏最初提交的⼀次
  • 服务端验证场景:表单验证须要服务端配合,只执⾏⼀段间断的输⼊事件的最初⼀次,还有搜寻联想词性能相似⽣存环境请⽤ lodash.debounce

节流函数的适⽤场景:

  • 拖拽场景:固定工夫内只执⾏⼀次,防⽌超⾼频次触发地位变动
  • 缩放场景:监控浏览器 resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

Set,Map 解构

ES6 提供了新的数据结构 Set。它相似于数组,然而成员的值都是惟一的,没有反复的值。Set 自身是一个构造函数,用来生成 Set 数据结构。ES6 提供了 Map 数据结构。它相似于对象,也是键值对的汇合,然而“键”的范畴不限于字符串,各种类型的值(包含对象)都能够当作键。

与缓存相干的 HTTP 申请头有哪些

强缓存:

  • Expires
  • Cache-Control

协商缓存:

  • Etag、If-None-Match
  • Last-Modified、If-Modified-Since

渲染过程中遇到 JS 文件如何解决?

JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行结束,浏览器再从中断的中央复原持续解析文档。也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性。

Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行程序?

Node 中的 Event Loop 和浏览器中的是齐全不雷同的货色。

Node 的 Event Loop 分为 6 个阶段,它们会依照 程序 重复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到零碎设定的阈值,就会进入下一阶段。

(1)Timers(计时器阶段):首次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(蕴含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Pending callbacks 阶段。

(2)Pending callbacks:执行推延到下一个循环迭代的 I / O 回调(零碎调用相干的回调)。

(3)Idle/Prepare:仅供外部应用。

(4)Poll(轮询阶段)

  • 当回调队列不为空时:会执行回调,若回调中触发了相应的微工作,这里的微工作执行机会和其余中央有所不同,不会等到所有回调执行结束后才执行,而是针对每一个回调执行结束后,就执行相应微工作。执行完所有的回调后,变为上面的状况。
  • 当回调队列为空时(没有回调或所有回调执行结束):但如果存在有计时器(setTimeout、setInterval 和 setImmediate)没有执行,会完结轮询阶段,进入 Check 阶段。否则会阻塞并期待任何正在执行的 I / O 操作实现,并马上执行相应的回调,直到所有回调执行结束。

(5)Check(查问阶段):会查看是否存在 setImmediate 相干的回调,如果存在则执行所有回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Close callbacks 阶段。

(6)Close callbacks:执行一些敞开回调,比方 socket.on(‘close’, …)等。

上面来看一个例子,首先在有些状况下,定时器的执行程序其实是 随机

setTimeout(() => {    console.log('setTimeout')}, 0)setImmediate(() => {    console.log('setImmediate')})

对于以上代码来说,setTimeout 可能执行在前,也可能执行在后

  • 首先 setTimeout(fn, 0) === setTimeout(fn, 1),这是由源码决定的
  • 进入事件循环也是须要老本的,如果在筹备时候破费了大于 1ms 的工夫,那么在 timer 阶段就会间接执行 setTimeout 回调
  • 那么如果筹备工夫破费小于 1ms,那么就是 setImmediate 回调先执行了

当然在某些状况下,他们的执行程序肯定是固定的,比方以下代码:

const fs = require('fs')
fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
    }, 0)
    setImmediate(() => {console.log('immediate')
    })
})

在上述代码中,setImmediate 永远 先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行结束后队列为空,发现存在 setImmediate 回调,所以就间接跳转到 check 阶段去执行回调了。

下面都是 macrotask 的执行状况,对于 microtask 来说,它会在以上每个阶段实现前 清空 microtask 队列,

setTimeout(() => {console.log('timer21')
}, 0)
Promise.resolve().then(function() {console.log('promise1')
})

对于以上代码来说,其实和浏览器中的输入是一样的,microtask 永远执行在 macrotask 后面。

最初来看 Node 中的 process.nextTick,这个函数其实是独立于 Event Loop 之外的,它有一个本人的队列,当每个阶段实现后,如果存在 nextTick 队列,就会 清空队列中的所有回调函数,并且优先于其余 microtask 执行。

setTimeout(() => {console.log('timer1')
 Promise.resolve().then(function() {console.log('promise1')
 })
}, 0)
process.nextTick(() => {console.log('nextTick')
 process.nextTick(() => {console.log('nextTick')
   process.nextTick(() => {console.log('nextTick')
     process.nextTick(() => {console.log('nextTick')
     })
   })
 })
})

对于以上代码,永远都是先把 nextTick 全副打印进去。

正文完
 0