原文转载自 https://neoteric.eu/blog/when-async-is-not-enough-introduction-to-multithreading-in-the-browser/
先说最重要的: JavaScript 代码可以异步执行, 但并不意味着它是跑在多个线程里. 那么异步到底是什么意思? 让我们想象发一个 Ajax 请求, 向服务端请求数据. 你并不是立即得到响应 -- 你需要等待一小段时间, 让服务端返回数据. 在等待响应的过程中, 程序运行着你其他部分的代码. 如果不是这样, Ajax 请求会冻结住, 不让后面的代码执行, 直到收到服务端的响应 -- 这不是我们想要的, 对吧?
事件循环(Event Loop)
在 JavaScript 运行环境中, 有个非常重要的概念, 叫事件循环. 它周而复始地工作着, 每一次循环被称为一个 "tick". 如果在某一个 tick 中, 有等待着的事件队列需要处理, 那么它们会一个个地被执行. 大家所熟知的 setTimeout 函数就是一个很好的例子. 它的第一个参数是一个回调函数 -- 一个在某段时间之后被执行的函数. 当 setTimeout 被解析时, 它被压入函数调用栈的栈顶, 它设置一个定时器, 然后就从栈顶弹出, 把你的回调函数塞到事件循环的后面 -- 那意味着这个回调函数不会精确地在定义的时间间隔后执行 -- 在事件队列中等待的其他事件需要被优先处理. 当时机到来, 你的回调函数被压入函数调用栈的栈顶, 然后执行. 你发向服务器的请求, 也是同样的原理 -- 你定义一个回调函数, 当收到响应后, 它被塞进事件循环队列的后面.
函数调用栈(Call Stack)
函数调用栈是一个底层的数据结构 -- 它记录我们运行到程序哪儿了. 当程序进入一个函数, 就把它放在栈顶, 当从函数中返回, 就意味着把它从栈中弹出. 让我们使用一点递归式的逻辑来简单展示一下:
- function factorial(n) {
- if(n === 1 || n === 0) {
- return 1;
- }
- return factorial(n - 1) * n;
- }
- console.log(factorial(3)); // 3! = 6
webWorkers
你已经看到, 异步代码, 解决的是一件事情 "现在发生" 还是 "以后发生", 而不是解决如何让 "多个事情同时发生". 但如果有一些处理器密集型任务, 我们担心它会让界面卡住, 怎么办?
答案是 WebWorkers. 它允许 JavaScript 代码在后台以一个独立的线程被执行. 它允许主线程流畅运行, 不被阻塞. WebWorkers 在另一个与 Windows 不同的全局上下文环境中. 这也带来了一些局限: 比如, 你不能直接在 Worker 里操作 DOM. 最基础的(也是浏览器支持得最好的)WebWorker 类型是 Dedicated Worker.
想创建一个 Worker, 你需要向 Worker 构造函数传入一个文件名, 在该文件中包含了需要执行的 JavaScript 脚本.
- // 在主线程
- var factorialWorker = new Worker('factorial.worker.js');
比如说, 我们想得到一整组数字的阶乘.
想向 Worker 传数据, 你需要调用 postMessage 方法:
- // 在主线程
- var arr = [50, 100, 125, 150];
- for(var i = 0; i <arr.length; ++i) {
- factorialWorker.postMessage(arr[i]);
- }
你可以通过事件在主线程和 Worker 线程之间通信. 如果你想监听 Worker 的返回值, 就在主线程注册一个事件监听器.
- // 在主线程
- factorialWorker.addEventListener('message', function(event) {
- console.log('!' + event.data.number + '=' + event.data.factorial);
- });
这会输出传入给 Worker 的数字的阶乘.
剩下唯一要做的事情就是创建 factorial.workder.JS 文件.
它需要返回当前计算的数字的阶乘, 还要定义计算阶乘的函数本身.
在 Worker 中, 有一个 self 属性. 它返回指向 WorkerGlobalScope 的引用. 利用它, 我们可以和向 Worker 发送数据的脚本通信.
- // factorial.workder.JS
- function factorial(n) {
- if(n === 1 || n === 0) {
- return 1;
- }
- return factorial(n - 1) * n;
- }
- self.addEventListener('message', function(event) {
- self.postMessage({ number: event.data, factorial: factorial(event.data) });
- });
这里发生的情况是, 我们创建了一个新的 Worker, 并监听它给我们返回的数据. 然后, 我们向它发送数据 --Worker 会得到数据, 在完成它内部的计算之后, 向我们发送一个响应. 所有的计算都在一个单独线程中完成. 很酷吧?
不过你可能会遇到一些问题. 第一个问题是 Chrome 不能以本地文件的方式使用 WebWorkers. 不过你可以开启一个 http 服务器 https://www.npmjs.com/package/http-server 来尝试使用它.
Webpack
另一个问题可能在你使用 Webpack 时出现. 它可能会给你一个 404 Not Found 错误, 因为它不知道你想以 WebWorker 的形式加载文件. 你需要额外的加载器 (loader) 来加载类似的文件. 让我带你看看这个过程. 首先, 用 NPM 安装加载器:
NPM install --save-dev worker-loader
然后你需要在 webpack.config.JS 中添加一条规则:
- module: {
- rules: [
- {
- test: /\.worker\.JS$/,
- use: { loader: 'worker-loader' }
- },
- (...)
- ]
- }
现在, 如果你引入以. workder.JS 结尾的文件, Webpack 会使用 worker-loader 来加载. 让我们用 ES6 的一些特性来修改一下代码:
- import FactorialWorker from './factorial.worker.js';
- const factorialWorker = new FactorialWorker();
- factorialWorker.addEventListener('message', event => {
- console.log(`!${event.data.number} = ${event.data.factorial}`);
- });
- const arrayOfNumbers = [50, 100, 125, 150];
- for(let number of arrayOfNumbers) {
- factorialWorker.postMessage(number);
- }
总结一下, 当开发一个背后有很多操作 (尤其是密集型计算) 的富应用时, WebWorkers 会非常有帮助. 尝试一下, 亲自看看吧. 我鼓励你去试验.
来源: https://www.cnblogs.com/powertoolsteam/p/10155319.html