{"action":"opened","issue":{"url":"https://api.github.com/repos/MashiroWang/Mashiro-s-Issue-Blog/issues/1","repository_url":"https://api.github.com/repos/MashiroWang/Mashiro-s-Issue-Blog","labels_url":"https://api.github.com/repos/MashiroWang/Mashiro-s-Issue-Blog/issues/1/labels{/name}","comments_url":"https://api.github.com/repos/MashiroWang/Mashiro-s-Issue-Blog/issues/1/comments","events_url":"https://api.github.com/repos/MashiroWang/Mashiro-s-Issue-Blog/issues/1/events","html_url":"https://github.com/MashiroWang/Mashiro-s-Issue-Blog/issues/1","id":315724630,"number":1,"title":"JavaScript 的运行机制","user":{"login":"MashiroWang","id":10394160,"avatar_url":"https://avatars0.githubusercontent.com/u/10394160?v=4","gravatar_id":"","url":"https://api.github.com/users/MashiroWang","html_url":"https://github.com/MashiroWang","followers_url":"https://api.github.com/users/MashiroWang/followers","following_url":"https://api.github.com/users/MashiroWang/following{/other_user}","gists_url":"https://api.github.com/users/MashiroWang/gists{/gist_id}","starred_url":"https://api.github.com/users/MashiroWang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MashiroWang/subscriptions","organizations_url":"https://api.github.com/users/MashiroWang/orgs","repos_url":"https://api.github.com/users/MashiroWang/repos","events_url":"https://api.github.com/users/MashiroWang/events{/privacy}","received_events_url":"https://api.github.com/users/MashiroWang/received_events","type":"User","site_admin":false},"labels":[{"id":906795606,"url":"https://api.github.com/repos/MashiroWang/Mashiro-s-Issue-Blog/labels/JavaScript","name":"JavaScript","color":"f7de3b","default":false}],"state":"open","locked":false,"assignee":{"login":"MashiroWang","id":10394160,"avatar_url":"https://avatars0.githubusercontent.com/u/10394160?v=4","gravatar_id":"","url":"https://api.github.com/users/MashiroWang","html_url":"https://github.com/MashiroWang","followers_url":"https://api.github.com/users/MashiroWang/followers","following_url":"https://api.github.com/users/MashiroWang/following{/other_user}","gists_url":"https://api.github.com/users/MashiroWang/gists{/gist_id}","starred_url":"https://api.github.com/users/MashiroWang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MashiroWang/subscriptions","organizations_url":"https://api.github.com/users/MashiroWang/orgs","repos_url":"https://api.github.com/users/MashiroWang/repos","events_url":"https://api.github.com/users/MashiroWang/events{/privacy}","received_events_url":"https://api.github.com/users/MashiroWang/received_events","type":"User","site_admin":false},"assignees":[{"login":"MashiroWang","id":10394160,"avatar_url":"https://avatars0.githubusercontent.com/u/10394160?v=4","gravatar_id":"","url":"https://api.github.com/users/MashiroWang","html_url":"https://github.com/MashiroWang","followers_url":"https://api.github.com/users/MashiroWang/followers","following_url":"https://api.github.com/users/MashiroWang/following{/other_user}","gists_url":"https://api.github.com/users/MashiroWang/gists{/gist_id}","starred_url":"https://api.github.com/users/MashiroWang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MashiroWang/subscriptions","organizations_url":"https://api.github.com/users/MashiroWang/orgs","repos_url":"https://api.github.com/users/MashiroWang/repos","events_url":"https://api.github.com/users/MashiroWang/events{/privacy}","received_events_url":"https://api.github.com/users/MashiroWang/received_events","type":"User","site_admin":false}],"milestone":null,"comments":0,"created_at":"2018-04-19T04:33:36Z","updated_at":"2018-04-19T04:33:36Z","closed_at":null,"author_association":"OWNER","body":"## JavaScript是单线程的，但浏览器并不是\r\n在浏览器中，每个窗口都只有一个 JavaScript 线程，所以既然是单线程，那么程序的执行顺序是从上到下依次执行，且在某个特定的时刻就只有一个特定的代码能够被执行，会阻塞其它的代码。\r\n那么就有人问了，既然 JavaScript 是单线程语言，那么异步调用是如何实现的？计时器是靠谁来计时？耗时的 I/O 操作为什么没把线程阻塞？\r\n这是因为虽然 JavaScript 运行在浏览器中是单线程的，每个窗口一个 JavaScript 线程，但是**浏览器并不是单线程的**。\r\n例如 Webkit 或是 Gecko 引擎，都可能有如下线程：\r\n\r\n* JavaScript引擎线程\r\n* 界面渲染线程\r\n* 浏览器事件触发线程\r\n* Http请求线程\r\n\r\n所谓的单线程可以理解是 JavaScript 引擎是单线程处理它的任务队列，包括普通函数和回调函数构成。\r\n\r\n\r\n<!--more-->\r\n\r\n\r\n下面是知乎上的一段回答\r\n\r\n> JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序。\r\n\r\n> 浏览器的内核是多线程的，它们在内核控制下相互配合以保持同步，一个浏览器至少实现三个常驻线程：JavaScript引擎线程，GUI渲染线程，浏览器事件触发线程。\r\n\r\n> 1. JavaScript引擎是基于事件驱动单线程执行的，JavaScript引擎一直等待着任务队列中任务的到来，然后加以处理，浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。\r\n> 2. GUI渲染线程负责渲染浏览器界面，当界面需要重绘（Repaint）或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意，GUI渲染线程与JavaScript引擎是互斥的，当JavaScript引擎执行时GUI线程会被挂起，GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。\r\n> 3. 事件触发线程，当一个事件被触发时该线程会把事件添加到待处理队列的队尾，等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等，但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理（当线程中没有执行任何同步代码的前提下才会执行异步代码）。\r\n\r\n## 同步任务与异步任务\r\n所有任务可以分为同步任务与异步任务两种。\r\n\r\n* 同步任务指在主线程上排队执行的任务，只有当前一个任务执行完毕，下一个任务才会执行。\r\n* 异步任务不进入主线程，而存放在任务队列中，只有当任务队列通知主线程某个异步任务可以执行后，这个异步任务才会进入主线程执行。\r\n\r\n## Event Loop\r\n\r\n在 JavaScript 中，异步执行的运行机制基本为三个步骤：\r\n\r\n1. 所有同步任务都在主线程上执行，形成一个执行栈。\r\n2. 在主线程外还存在一个任务队列，只要异步任务有了运行结果，就在事件队列中放置一个事件。\r\n3. 一旦执行栈中的所有同步任务全部执行完毕，系统就会读取事件队列中的事件，事件对应的异步任务结束等待状态，进入执行栈开始执行。\r\n\r\n举个例子，拿 ajax 来说，当页面的单线程执行 `xhr.send()` 之后，对于页面来说发送任务已经完成了。怎么发送那是浏览器的事，和单线程无关。什么时候响应，这事说不准。为了及时地得到响应的内容，在单线程中注册相应的事件就好 `xhr.onreadystatechange = fn() {...}` 。注册之后，浏览器会在内部的其他线程中自动地帮我们监听该事件。直到该事件被触发，浏览器会在任务队列中添加一个任务等待该单线程执行。\r\n\r\n## Macrotask 与 Microtask\r\n上面所说的只是 Event Loop 的基本概念，具体我们引入两个新概念 `Macrotask` 和 `Mircotask`。在事件队列中，不同的异步任务是被存入不同的队列中的，而且它们也并不是简单的先进先出。\r\n\r\n![](https://i.loli.net/2017/09/04/59ad06ee648bf.png)\r\n\r\n一个 Event Loop 中会有一个正在执行的任务，而这个任务就是从 Macrotask 队列中来的。\r\n\r\n> 按照 [WHATWG](https://html.spec.whatwg.org/multipage/webappapis.html#task-queue) 规范，每一次事件循环（one cycle of the event loop），只处理一个 (macro)task。待该 macrotask 完成后，所有的 microtask 会在同一次循环中处理。处理这些 microtask 时，还可以将更多的 microtask 入队，它们会一一执行，直到整个 microtask 队列处理完。\r\n\r\n所以在同一个事件循环中，一次最多处理一个 Macrotask，却可以处理完所有的 Microtask。\r\n\r\n基本来说，当我们想以同步的方式来处理异步任务的时候就使用 Microtask，其他情况就直接用 Macrotask。\r\n\r\n这图基本能说明调用栈、后台线程、(Macro)task Queue 与 Microtask Queue 的流程关系了。\r\n\r\n![](https://ooo.0o0.ooo/2017/09/04/59ad42a27241a.png)\r\n\r\n\r\n### 举个例子\r\n\r\n\r\n#### 例子1\r\n\r\n``` js\r\n(function () {\r\n    setTimeout(function() {console.log(4)}, 0);\r\n    new Promise(function executor(resolve) {\r\n        console.log(1);\r\n        for( var i=0 ; i<10000 ; i++ ) {\r\n            i == 9999 && resolve();\r\n        }\r\n        console.log(2);\r\n    }).then(function() {\r\n        console.log(5);\r\n    });\r\n    console.log(3);\r\n})()\r\n```\r\n\r\n1. 当前 microtask 执行，`setTimeout` 中的 callback 函数被放入 microtask queue 中（即使时间为0ms）。\r\n2. 实例化 promise，输出1。\r\n3. 循环10000次之后，promise 的状态变为 resolved，这个 `promise.then()` 中的回调放入当前事件循环回合中的 microtask queue。\r\n4. 输出3。\r\n5. 当前 macrotask 执行完毕，执行 microtask queue，输出5，当前 microtask queue 执行完毕。\r\n6. 执行下一个 macrotask，输出4。\r\n\r\n\r\n#### 例子2\r\n个人觉得下面这段代码的理解更加深刻，可以仔细思考一下它的执行顺序：\r\n\r\n``` js\r\nconsole.log('start')\r\n\r\nconst interval = setInterval(() => {  \r\n  console.log('setInterval')\r\n}, 0)\r\n\r\nsetTimeout(() => {  \r\n  console.log('setTimeout 1')\r\n  Promise.resolve()\r\n      .then(() => {\r\n        console.log('promise 3')\r\n      })\r\n      .then(() => {\r\n        console.log('promise 4')\r\n      })\r\n      .then(() => {\r\n        setTimeout(() => {\r\n          console.log('setTimeout 2')\r\n          Promise.resolve()\r\n              .then(() => {\r\n                console.log('promise 5')\r\n              })\r\n              .then(() => {\r\n                console.log('promise 6')\r\n              })\r\n              .then(() => {\r\n                clearInterval(interval)\r\n              })\r\n        }, 0)\r\n      })\r\n}, 0)\r\n\r\nPromise.resolve()\r\n    .then(() => {  \r\n        console.log('promise 1')\r\n    })\r\n    .then(() => {\r\n        console.log('promise 2')\r\n    })\r\n```\r\n1. 输出 start\r\n2. setInterval，setTimeout，Promise.resolve 加入 macrotask queue。\r\n3. 调用栈变空，microtask queue 开始执行，输出 promise1 和 promise2。\r\n4. microtask queue 清空，setInterval 回调执行，输出 setInterval，又一个 setInterval 加入 macrotask queue。\r\n5. setTimeout 回调执行，输出 setTimeout1，Promise.resolve 加入 macrotask queue，then() 中的回调函数加入 microtask queue。\r\n6. 调用栈变空，microtask queue 执行，输出 promise3 和 promise4，setTimeout 加入 microtask queue。\r\n7. microtask queue 清空，setInterval 回调再次执行，输出 setInterval，又一个 setInterval 加入 macrotask queue。\r\n8. setTimeout 回调执行，输出 setTimeout2，Promise.resolve 加入 macrotask queue，then() 中的回调函数加入 microtask queue。\r\n9. 调用栈变空，microtask queue 执行，输出 promise5 和 promise6，并且执行 clearInterval 清除 Interval。\r\n\r\n### 具体实现\r\n\r\n* macrotasks: setTimeout setInterval setImmediate I/O UI渲染\r\n* microtasks: Promise process.nextTick Object.observe MutationObserver\r\n\r\n### 具体流程\r\n当执行栈为空的时候，开始依次执行：\r\n\r\n1. 把最早的任务(task A)放入任务队列\r\n2. 如果 task A 为null (那任务队列就是空)，直接跳到第6步\r\n3. 将 currently running task 设置为 task A\r\n4. 执行 task A (也就是执行回调函数)\r\n5. 将 currently running task 设置为 null 并移出 task A\r\n6. 执行 microtask 队列\r\n   - a. 在 microtask 中选出最早的任务 task X\r\n   - b. 如果 task X 为null (那 microtask 队列就是空)，直接跳到 g\r\n   - c. 将 currently running task 设置为 task X\r\n   - d. 执行 task X\r\n   - e. 将 currently running task 设置为 null 并移出 task X\r\n   - f. 在 microtask 中选出最早的任务 , 跳到 b\r\n   - g. 结束 microtask 队列 \r\n7. 跳到第一步\r\n\r\n## 参考文章\r\n1. [JavaScript 运行机制详解：再谈Event Loop](http://www.ruanyifeng.com/blog/2014/10/event-loop.html)\r\n2. [理解事件循环二(macrotask和microtask)](https://github.com/ccforward/cc/issues/48)\r\n3. [Understanding the Node.js Event Loop](https://blog.risingstack.com/node-js-at-scale-understanding-node-js-event-loop/)\r\n4. [Javascript是单线程的深入分析](https://www.cnblogs.com/Mainz/p/3552717.html)\r\n"}}
{"actor":{"display_login":"MashiroWang"}}