关于前端:jsliang-求职系列-06-Event-Loop

7次阅读

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

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 单线程和多线程
四 Event Loop
 4.1 Event Loop 执行过程
 4.2 requestAnimationFrame
  4.2.1 requestAnimationFrame 介绍
  4.2.2 requestAnimationFrame 应用原因
 4.3 Web Worker
  4.3.1 Web Worker 应用
  4.3.2 Web Worker 数据通讯
  4.3.3 Web Worker 可操作 API
  4.3.4 Web Worker 兼容性
 4.4 Node 和 浏览器
五 两个环境 Event Loop 比照
六 题目训练
 6.1 同步工作
 6.2 定时器
 6.3 定时器 + Promise
 6.4 综合
七 参考文献
 7.1 requestAnimationFrame 参考文献
 7.2 Web Worker 参考文献
 7.3 其余参考文献

二 前言

返回目录

Event Loop 即事件循环,是指浏览器或 Node 的一种解决 JavaScript 单线程运行时不会阻塞的一种机制,也就是咱们常常应用异步的原理。

三 单线程和多线程

返回目录

JavaScript 是一个单线程的语言。

单线程在程序执行时,所走的程序门路依照间断程序排下来,后面的必须解决好,前面的才会执行。

以 Chrome 浏览器中为例,当你关上一个 Tab 页时,其实就是创立了一个过程。

一个过程中能够有多个线程,比方渲染线程、JS 引擎线程、HTTP 申请线程等等。

当你发动一个申请时,其实就是创立了一个线程,当申请完结后,该线程可能就会被销毁。

  • 浏览器内核是怎么的?

浏览器内核是多线程的,在内核管制下各线程相互配合以放弃同步,一个浏览器通常由以下常驻线程组成:

  1. GUI 渲染线程:解析 HTML、CSS 等。在 JavaScript 引擎线程运行脚本期间,GUI 渲染线程处于挂起状态,也就是被“解冻”了。
  2. JavaScript 引擎线程:负责解决 JavaScript 脚本。
  3. 定时触发器线程setTimeoutsetInterval 等。事件触发线程会将计数结束后的事件退出到工作队列的尾部,期待 JS 引擎线程执行。
  4. 事件触发线程:负责将筹备好的事件交给 JS 引擎执行。
  5. 异步 http 申请线程:负责执行异步申请之类函数的线程,例如 Promise.then()ajax 等。
  • 为什么不设计成多线程?

假如有个 DOM 节点,当初有线程 A 操作它,删除了这个 DOM

而后线程 B 又操作它,批改了这个 DOM 某局部。

那么当初问题来了,咱听谁的?

所以罗唆设计成一个单线程,平安稳当不出事。

哪怕前期 HTML5 出了个 Web Worker 也是不容许操作 DOM 构造的,能够实现一些分布式的计算。

Web Worker 在本文中有解说

  • 为什么须要异步?

这时候又有问题了,如果调用某个接口(Ajax),或者加载某张图片的时候,咱们卡住了,这样页面是不是就始终不能渲染?

而后因为单线程只能先让后面的程序走完,即使这个接口或者图片缓过去了,我上面还有其余工作没做呢,这不就卡死了么?

所以这时候异步来了:

在波及某些须要期待的操作的时候,咱们就抉择让程序持续运行。

期待接口或者图片返回过去后,就告诉程序我做好了,你能够持续调用了。

四 Event Loop

返回目录

  • 为什么会有 Event Loop?

后面 jsliang 讲到:JavaScript 线程一次只能做一件事。

如果碰到一些须要期待的程序,例如 setTimeout 等,那就歇菜了。

所以,JavaScript 为了协调事件、用户交互、脚本、渲染、网络等,就搞进去一个 事件循环(Event Loop)

  • 什么是 Event Loop?

JavaScript 从 script 开始读取,而后一直循环,从“工作队列”中读取执行事件的过程,就是 事件循环(Event Loop)

4.1 Event Loop 执行过程

返回目录

Event Loop 执行过程如下:

  1. 一开始整个脚本 script 作为一个宏工作执行
  2. 执行过程中,同步代码 间接执行, 宏工作 进入宏工作队列, 微工作 进入微工作队列。
  3. 以后宏工作执行完出队,查看微工作列表,有则顺次执行,直到全副执行结束。
  4. 执行浏览器 UI 线程的渲染工作。
  5. 查看是否有 Web Worker 工作,有则执行。
  6. 执行完本轮的宏工作,回到步骤 2,顺次循环,直到宏工作和微工作队列为空。

事件循环中的异步队列有两种:宏工作队列(MacroTask)和 微工作队列(MicroTask)。

Web Worker 是运行在后盾的 JS,独立于其余脚本,不会影响页面的性能。

宏工作队列能够有多个,微工作队列只有一个。

宏工作 包含:

  • script
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI rendering

微工作 包含:

  • MutationObserver
  • Promise.then()/catch()
  • Promise 为根底开发的其余技术,例如 fetch API
  • V8 的垃圾回收过程
  • Node 独有的 process.nextTick

4.2 requestAnimationFrame

返回目录

4.2.1 requestAnimationFrame 介绍

返回目录

window.requestAnimationFrame() 通知浏览器——你心愿执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

该办法须要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

requestAnimationFrame 简称 rAF

咱们看一下它应用状况:

<body>
  <div class="animation"> 动画元素 </div>

  <script>
    window.onload = function() {const element = document.querySelector('.animation'); 
      let start;

      function step(timestamp) {if (start === undefined) {start = timestamp;}
        const elapsed = timestamp - start;

        // 这里应用 Math.min() 确保元素刚好停在 200px 的地位。element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';

        // 在两秒后进行动画
        if (elapsed < 2000) {window.requestAnimationFrame(step);
        }
      }

      window.requestAnimationFrame(step);
    };
  </script>
</body>

4.2.2 requestAnimationFrame 应用原因

返回目录

如果咱们应用 setTimeout 来实现动画成果,那么咱们会发现在某些低端机上呈现卡顿、抖动的景象,它产生的起因是:

  • setTimeout 的执行事件并不是确定的。它属于宏工作队列,只有当主线程上的工作执行结束后,才会调用队列中的工作判断是否开始执行。
  • 刷新频率受屏幕分辨率和屏幕尺寸影响,因而不同设施的刷新频率不同,而 setTimeout 只能固定一个工夫距离刷新。

在下面 Event Loop 的过程中,咱们晓得执行完微工作队列会有一步操作:

  • 执行浏览器 UI 线程的渲染工作。

requestAnimationFrame 就在这里边执行,就不会等宏工作队列的排队,从而导致卡顿等问题了。

4.3 Web Worker

返回目录

Web Worker 为 Web 内容在后盾线程中运行脚本提供了一种简略的办法。

如咱们所知,JavaScript 始终是属于单线程环境,咱们无奈同时运行两个 JavaScript 脚本。

然而试想一下,如果咱们能够同时运行两个(或者多个)JavaScript 脚本,一个来解决 UI 界面(始终以来的用法),一个来解决一些简单计算,那么性能就会更好。

在 HTML5 的新标准中,实现了 Web Worker 来引入 JavaScript 的“多线程”技术,他的能力让咱们能够在页面主运行的 JavaScript 线程中加载运行另外独自的一个或者多个 JavaScript 线程。

留神:JavaScript 实质上还是单线程的,Web Worker 只是浏览器(宿主环境)提供的一个得力 API。

4.3.1 Web Worker 应用

返回目录

调用 Web Worker:

index.js

console.log('index- 同步工作');
Promise.resolve().then((res) => {console.log('index-Promise');
});
setTimeout(() => {console.log('index-setTimeout');
}, 1000);

index.html

<script>
  window.onload = function() {console.log('本地 - 同步工作');
    // 微工作之间
    Promise.resolve().then((res) => {console.log('本地 - 微工作 1');
    })
    const worker1 = new Worker('./index.js');
    Promise.resolve().then((res) => {console.log('本地 - 微工作 2');
    })

    // 宏工作之间
    setTimeout(() => {console.log('本地 - 宏工作 1');
    }, 1000);
    const worker2 = new Worker('./index.js');
    setTimeout(() => {console.log('本地 - 宏工作 2');
    }, 1000);
  };
</script>

执行的时候打印后果:

本地 - 同步工作
本地 - 微工作 1
本地 - 微工作 2
index- 同步工作
index-Promise
index- 同步工作
index-Promise
本地 - 宏工作 1
本地 - 宏工作 2
index-setTimeout
index-setTimeout

能够看到:

  1. 先执行 script 中同步工作
  2. 再执行 script 中微工作
  3. 而后执行 UI 线程的渲染工作(这里在代码中没有体现,感兴趣的能够试试增加 rAF
  4. 接着才执行 Web Worker 外面内容
  5. 再来是 index.html 中的宏工作
  6. 最初才是 Web Worker 文件中的宏工作

能够看出它仍合乎 Event Loop 流程。

4.3.2 Web Worker 数据通讯

返回目录

index.js

onmessage = (res) => {
  // Worker 接收数据
  console.log('Worker 收到数据:', res);
  // Worker 收到数据:// MessageEvent {isTrusted: true, data: "查房,这里是 index.html!", origin: "", lastEventId:"", source: null, …}

  // Worker 发送数据
  postMessage('开门!这里是 index.js');
}

index.html

<script>
window.onload = function() {
  // 实例化 Worker
  const worker = new Worker('./index.js');

  // index.html 接收数据
  worker.addEventListener('message', (res) => {console.log('index.html 收到数据:', res);
  // index.html 收到数据:// MessageEvent {isTrusted: true, data: "开门!这里是 index.js", origin: "", lastEventId:"", source: null, …}
  });

  // index.html 发送数据
  worker.postMessage('查房,这里是 index.html!');

  //  终止 Worker
  worker.terminate();};
</script>

index.html 中,通过:

  • worker.addEventListener('message', callback)。接管 Web Worker 传递的数据。
  • worker.postMessage('xxx')。发送数据给 Web Worker。
  • worker.terminate()。终止通信

index.js 中,通过:

onmessage = (res) => {console.log(res); // 在 onmessage 办法承受数据
  postMessage('xxx'); // 通过 postMessage 发送数据
}

4.3.3 Web Worker 可操作 API

返回目录

  • setTimeout(),clearTimeout(),setInterval(),clearInterval():有了这几个函数,就能够在 Web Worker 线程中执行定时操作了;
  • XMLHttpRequest 对象:意味着咱们能够在 Web Worker 线程中执行 Ajax 申请;
  • navigator 对象:能够获取到 ppNameappVersionplatformuserAgent 等信息;
  • location 对象(只读):能够获取到无关以后 URL 的信息;

如果须要加载其余 JS 脚本:

importScripts('./index2.js', './index3.js');

// 或者

// importScripts('./index2.js');
// importScripts('./index3.js');

4.3.4 Web Worker 兼容性

返回目录

  • IE:11 版本
  • Edge:14+ 版本
  • Firefox:51+ 版本
  • Chrome:56+ 版本
  • 其余:看 caniuse 链接

4.4 Node 和 浏览器

返回目录

为啥会有 浏览器 Event LoopNode.js Event Loop

简略来说:

  • 你的页面放到了浏览器去展现,你的数据放到了后盾解决(将 Node.js 看成 PHP、Java 等后端语言),这两者能没有区别么?!

再认真一点:

  • Node.js:Node.js 的 Event Loop 是基于 libuvlibuv 曾经对 Node.js 的 Event Loop 作出了实现。
  • 浏览器:浏览器的 Event Loop 是基于 HTML5 标准 的。而 HTML5 标准中只是定义了浏览器中的 Event Loop 的模型,具体实现留给了浏览器厂商。

libuv 是一个多平台反对库,次要用于异步 I/O。它最后是为 Node.js 开发的,当初 LuvitJuliapyuv 和其余的框架也应用它。Github – libuv 仓库

所以,咱们得将这两个 Event Loop 辨别开来,它们是不一样的东东哈~

五 两个环境 Event Loop 比照

返回目录

浏览器环境下,microtask 的工作队列是每个 macrotask 执行完之后执行。

而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行结束,就会去执行 microtask 队列的工作。

这里没有讲 Node.js 的工夫循环机制,第一个是因为 jsliang 对 Node 不熟,怕瞎写误导;第二个是因为面试官问的时候,基本上答复的都是浏览器的事件循环机制,偶然提一嘴 Event Loop 分为浏览器事件循环和 Node 事件循环算是加点小分了。

六 题目训练

返回目录

在训练之前,咱们先讲下考题范畴:

  • 同步工作:碰到间接执行,不要管三七二十一。
  • 宏工作scriptsetTimeout
  • 微工作Promise.then()async/await

临时就这么点内容,想来不会考错!

6.1 同步工作

返回目录

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

function foo() {console.log('foo');
  bar();}

foo();

这段内容输入啥?

  • foo -> bar

详情不须要解释。

6.2 定时器

返回目录

console.log("1");

setTimeout(function () {console.log("2");
}, 0);

setTimeout(function () {console.log("3");
}, 2000);

console.log("4");
  • 宏工作队列:scriptsetTimeout(2)setTimeout(3)
  • 微工作队列:无

所以输入:

1
4
2
3

6.3 定时器 + Promise

返回目录

  • 题目 1:请输入上面代码打印状况
console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {console.log('promise1');
}).then(function() {console.log('promise2');
});

console.log('script end');

script 宏工作下:

  • 宏工作 setTimeout
  • 微工作 .then(promise1)

所以先执行同步代码,先输入:script start -> script end

而后调用微工作,输入 promise1,将 then(promise2) 放入微工作。

再次调用微工作,将 promise2 输入。

最初调用宏工作 setTimeout,输入 setTimeout

因而输入程序:

script start
script end
promise1
promise2
setTimeout

  • 题目 2:请输入上面代码打印状况
Promise.resolve().then(function promise1() {console.log('promise1');
})

setTimeout(function setTimeout1() {console.log('setTimeout1')
  Promise.resolve().then(function promise2() {console.log('promise2');
  })
}, 0)

setTimeout(function setTimeout2() {console.log('setTimeout2')
}, 0)

script 宏工作下:

  • 同步工作:无
  • 微工作:Promise.then(promise1)
  • 宏工作:setTimeout(setTimeout1)setTimeout(setTimeout2)

所以先走同步工作,发现并没有,不理睬。

而后再走微工作 Promise.then(promise1),输入 promise1

接着推出宏工作,先走 setTimeout(setTimeout1)

  • 同步工作:console.log('setTimeout1')
  • 微工作:Promise.then(promise2)
  • 宏工作:setTimeout(setTimeout2)(留神这里的宏工作是整体的)

所以先走同步工作,输入 setTimeout1

接着走微工作,输入 promise2

而后推出宏工作 setTimeout(setTimeout2)

setTimeout(setTimeout2) 环境下的微工作和宏工作都没有,所以走完同步工作,输入 setTimeout2,就完结了。

因而,输入程序:

promise1
setTimeout1
promise2
setTimeout2

  • 题目 3:请输入上面代码打印状况
setTimeout(function() {console.log(4);
}, 0);

const promise = new Promise((resolve) => {console.log(1);
  for (var i = 0; i < 10000; i++) {i == 9999 && resolve();
  }
  console.log(2);
}).then(function() {console.log(5);
});

console.log(3);

script 下:

  • 同步工作:console.log(1)console.log(2)console.log(3)
  • 微工作:Promise.then()(等到 9999 再增加进来)
  • 宏工作 setTimeout

所以先走同步工作,留神当咱们 new Promsie() 的时候,外部的代码会执行的,跟同步工作一样的,而 .then()resolve() 的状况下才会增加到微工作。

因而先输入 1 -> 2 -> 3

而后推出微工作 Promise.then(),所以输入 5。

最初推出宏工作 setTimeout,输入 4。

后果程序为:

1
2
3
5
4

6.4 综合

返回目录

综合题目就不给答案解析了,请自行脑补。


  • 题目 1:请输入上面代码打印状况
setTimeout(function () {console.log('timeout1');
}, 1000);

console.log('start');

Promise.resolve().then(function () {console.log('promise1');
  Promise.resolve().then(function () {console.log('promise2');
  });
  setTimeout(function () {Promise.resolve().then(function () {console.log('promise3');
    });
    console.log('timeout2')
  }, 0);
});

console.log('done');

后果:

start
done
promise1
promise2
timeout2
promise3
timeout1

  • 题目 2:请输入上面代码打印状况
console.log("script start");

setTimeout(function() {console.log("setTimeout---0");
}, 0);

setTimeout(function() {console.log("setTimeout---200");
  setTimeout(function() {console.log("inner-setTimeout---0");
  });
  Promise.resolve().then(function() {console.log("promise5");
  });
}, 200);

Promise.resolve()
.then(function() {console.log("promise1");
})
.then(function() {console.log("promise2");
});

Promise.resolve().then(function() {console.log("promise3");
});

console.log("script end");

输入:

script start
script end
promise1
promise3
promise2
setTimeout---0
setTimeout---200
promise5
inner-setTimeout---0

  • 题目 3:请输入上面代码打印状况
console.log(1);

setTimeout(() => {console.log(2);

  new Promise((resolve) => {console.log(3);
  }).then(() => {console.log(4);
  });
}, 200);

new Promise((resolve) => {console.log(5);
  resolve();}).then(() => {console.log(6);
});

setTimeout(() => {console.log(7);
}, 0);

setTimeout(() => {console.log(8);

  new Promise(function (resolve) {console.log(9);
    resolve();}).then(() => {console.log(10);
  });
}, 100);

new Promise(function (resolve) {console.log(11);
  resolve();}).then(() => {console.log(12);
});

console.log(13);

输入:

1
5
11
13
6
12
7
8
9
10
2
3

七 参考文献

返回目录

  • [x] 浏览器与 Node 的事件循环 (Event Loop) 有何区别?【浏览倡议:20min】
  • [x] 一次弄懂 Event Loop(彻底解决此类面试问题)【浏览倡议:20min】
  • [x] 事件循环机制的那些事【浏览倡议:10min】
  • [x] 深刻了解 js 事件循环机制(Node.js 篇)【浏览倡议:无】
  • [x] 详解 JavaScript 中的 Event Loop(事件循环)机制【浏览倡议:5min】
  • [x] 深刻了解 JavaScript Event Loop【浏览倡议:20min】
  • [x]【THE LAST TIME】彻底吃透 JavaScript 执行机制【浏览倡议:20min】
  • [x] JavaScript:彻底了解同步、异步和事件循环(Event Loop)【浏览倡议:10min】
  • [x] 从 event loop 标准探索 javaScript 异步及浏览器更新渲染机会【浏览倡议:20min】
  • [x] Tasks, microtasks, queues and schedules【浏览倡议:无】
  • [x] The Node.js Event Loop, Timers, and process.nextTick()【浏览倡议:无】

7.1 requestAnimationFrame 参考文献

返回目录

  • [x] 再谈谈 Promise, setTimeout, rAF, rIC【浏览倡议:10min】
  • [x] window.requestAnimationFrame【浏览倡议:10min】

7.2 Web Worker 参考文献

返回目录

  • [x] JavaScript 中的多线程 — Web Worker【浏览倡议:30min】
  • [x] 浅谈 HTML5 Web Worker【浏览倡议:10min】
  • [x] JavaScript 性能利器 —— Web Worker【浏览倡议:10min】

7.3 其余参考文献

返回目录

  • [x] 浏览器过程?线程?傻傻分不清楚!【浏览倡议:5min】

jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。

正文完
 0