1.JavaScript 为什么是单线程?
JavaScript 语言的一大特点就是单线程,也就是说,同一个工夫只能做一件事。那么,为什么 JavaScript 不能有多个线程呢?这样能提高效率啊。
JavaScript 的单线程,与它的用处无关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很简单的同步问题。比方,假设 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上增加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了防止复杂性,从一诞生,JavaScript 就是单线程,这曾经成了这门语言的外围特色,未来也不会扭转。
为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 规范,容许 JavaScript 脚本创立多个线程,然而子线程齐全受主线程管制,且不得操作 DOM。所以,这个新规范并没有扭转 JavaScript 单线程的实质。
2、执行机制相干知识点
2.1 同步工作、异步工作
同步工作
异步工作
有一天,张三要去做饭 这时候他要做两件事 别离是蒸米饭 和 抄菜,当初有两种形式去实现这个工作
A. 先去蒸米饭 而后等蒸米饭好了 再去抄菜 --- 同步工作
B. 先去蒸米饭 而后等蒸米饭的过程中 再去抄菜 --- 异步工作
其实这两个形式不就是 一种是同步工作(synchronous),另一种是异步工作(asynchronous)吗。同步工作指的是,在主线程上排队执行的工作,只有前一个工作执行结束,能力执行后一个工作;异步工作指的是,不进入主线程、而进入 ” 工作队列 ”(task queue)的工作,只有 ” 工作队列 ” 告诉主线程,某个异步工作能够执行了,该工作才会进入主线程执行。
当咱们关上网站时,网页的渲染过程就是一大堆同步工作,比方页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的工作,就是异步工作。,咱们用导图来阐明:
具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它能够被视为没有异步工作的异步执行。)
(1)所有同步工作都在主线程上执行,造成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个 ” 工作队列 ”(task queue)。只有异步工作有了运行后果,就在 ” 工作队列 ” 之中搁置一个事件。
(3)一旦 ” 执行栈 ” 中的所有同步工作执行结束,零碎就会读取 ” 工作队列 ”,看看外面有哪些事件。那些对应的异步工作,于是完结期待状态,进入执行栈,开始执行。
(4)主线程一直反复下面的第三步。
2.3、JavaScript 的宏工作与微工作
除了狭义上的定义,咱们能够将工作进行更精密的定义,分为宏工作与微工作:
宏工作(macro-task):包含整体代码 script 脚本的执行,setTimeout,setInterval,ajax,dom 操作 还有如 I/O 操作、UI 渲 染等
微工作(micro-task):Promise 回调 node 中的 process.nextTick、对 Dom 变动监听的 MutationObserver。
主线程都从 ” 工作队列 ” 中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为 Event Loop(事件循环)
咱们解释一下这张图:
同步和异步工作别离进入不同的执行 ” 场合 ”,同步的进入主线程,异步的进入 Event Table 并注册函数。
当指定的事件实现时,Event Table 会将这个函数移入 Event Queue。
主线程内的工作执行结束为空,会去 Event Queue 读取对应的函数,进入主线程执行。
上述过程会一直反复,也就是常说的Event Loop(事件循环)。(Event Loop 是 javascript 的执行机制)
3、总结
3.1 面试答复
面试中该如何答复呢?上面是我集体举荐的答复:
首先 js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保障代码的有序执行。
在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会始终期待其返回后果,而是会将这个事件挂起,继续执行执行栈中的其余工作
当同步事件执行结束后,再将异步事件对应的回调退出到与以后执行栈中不同的另一个工作队列中期待执行。
工作队列能够分为宏工作对列和微工作对列,当以后执行栈中的事件执行结束后,js 引擎首先会判断微工作对列中是否有工作能够执行,如果有就将微工作队首的事件压入栈中执行。
当微工作对列中的工作都执行实现后再去判断宏工作对列中的工作。
3.2 优先级
3.2.1 setTimeout()、setInterval()
setTimeout() 和 setInterval() 这两个函数,它们的外部运行机制齐全一样,区别在于前者指定的代码是一次性执行,后者则为重复执行。setTimeout() 和 setInterval() 产生的工作是 异步工作,也属于 宏工作。setTimeout() 承受两个参数,第一个是回调函数,第二个是推延执行的毫秒数。setInterval() 承受两个参数,第一个是回调函数,第二个是重复执行的毫秒数。如果将第二个参数设置为 0 或者不设置,意思 并不是立刻执行,而是指定某个工作在主线程最早可得的闲暇工夫执行,也就是说,尽可能早得执行。它在 "工作队列" 的尾部增加一个事件,因而要等到同步工作和 "工作队列" 现有的事件都解决完,才会失去执行。所以说,setTimeout() 和 setInterval() 第二个参数设置的工夫并不是相对的,它须要依据以后代码最终执行的工夫来确定的,简略来说,如果以后代码执行的工夫(如执行 200ms)超出了推延执行(setTimeout(fn, 100))或重复执行的工夫(setInterval(fn, 100)),那么 setTimeout(fn, 100) 和 setTimeout(fn, 0) 也就没有区别了,setInterval(fn, 100) 和 setInterval(fn, 0) 也就没有区别了。
3.2.2 Promise
Promise 相对来说就比拟非凡了,在 new Promise() 中传入的回调函数是会 立刻执行 的,然而它的 then() 办法是在 执行栈之后,工作队列之前 执行的,它属于 微工作。
3.2.3 process.nextTick
process.nextTick 是 Node.js 提供的一个与 "工作队列" 无关的办法,它产生的工作是放在 执行栈的尾部,并不属于 宏工作 和 微工作,因而它的工作 总是产生在所有异步工作之前。
3.2.4 setImmediate
setImmediate 是 Node.js 提供的另一个与 "工作队列" 无关的办法,它产生的工作追加到 "工作队列" 的尾部,它和 setTimeout(fn, 0) 很像,但优先级都是 setTimeout 优先于 setImmediate。有时候,setTimeout 的执行程序会在 setImmediate 的后面,有时候会在 setImmediate 的前面,这并不是 node.js 的 bug,这是因为尽管 setTimeout 第二个参数设置为 0 或者不设置,然而 setTimeout 源码中,会指定一个具体的毫秒数(node 为 1ms,浏览器为 4ms),而因为以后代码执行工夫受到执行环境的影响,执行工夫有所起伏,如果以后执行的代码小于这个指定的值时,setTimeout 还没到推延执行的工夫,天然就先执行 setImmediate 了,如果以后执行的代码超过这个指定的值时,setTimeout 就会先于 setImmediate 执行。
3.2.5 总结优先级 重点重点重点重点重点重点
通过下面的介绍,咱们就能够得出一个代码执行的优先级:
同步代码(宏工作)> process.nextTick > Promise(微工作)> setTimeout(fn)、setInterval(fn)(宏工作)> setImmediate(宏工作)> setTimeout(fn, time)、setInterval(fn, time),其中 time>0
4. 用实例来测验一下学习成绩
第一题:
setTimeout(function() {console.log(1) // 作为宏工作,暂不执行,放到工作队列中
}, 0);
new Promise(function(resolve, reject) {console.log(2); // 输入 2
resolve()}).then(function() {console.log(3) //then 回调函数作为微工作,暂不执行 放入工作队列
});
//// 以下代码须要在 node 环境中执行
process.nextTick(function () {console.log(4) // 暂不执行 放入工作队列
})
console.log(5) // 输入 5
题目解析:
第一轮:setTimeout 作为宏工作,暂不执行,放到工作队列中
执行 new Promise 输入 2 then 回调函数作为微工作,暂不执行 放入工作队列
继续执行 遇见 process.nextTick 同样将回调函数扔到为工作队列,再继续执行,输入 5
致此同步工作执行实现 依据后面失去的优先级 咱们接着执行 process.nextTick 输入 4
寻找工作队列的微工作 找到 then 回调函数输入 3 微工作执行结束
寻找宏工作 setTimeout 输入 1
后果:25431
复制代码
第二题
console.log(1); // 立刻输入 1
setTimeout(function () {console.log(2); // 作为宏工作,暂不执行,放到工作队列中
new Promise(function (resolve, reject) {console.log(3);
resolve();
console.log(4);
}).then(function () {console.log(5);
});
});
function fn() {console.log(6);
setTimeout(function () { // 作为宏工作,暂不执行,放到工作队列中
console.log(7);
}, 50);
}
new Promise(function (resolve, reject) {console.log(8);
resolve();
console.log(9);
}).then(function () { // 作为微工作,暂不执行
console.log(10);
});
fn();
console.log(11);
// 以下代码须要在 node 环境中执行
process.nextTick(function () {console.log(12);
});
setImmediate(function () {console.log(13);
});
复制代码
此时输入为:1 8 9 6 11 12
运行微工作:new Promise(function (resolve, reject) {// console.log(8); // 已执行
// resolve(); // 已执行
// console.log(9); // 已执行
})
.then(function () {console.log(10);
});
复制代码
此时输入为:10
读取 "工作队列" 的回调函数到 "执行栈":setTimeout(function () {console.log(2);
new Promise(function (resolve, reject) {console.log(3);
resolve();
console.log(4);
})
//.then(function () { // 作为微工作,暂不执行
// console.log(5);
//});
});
此时输入为:2 3 4
再运行微工作:
setTimeout(function () {// console.log(2); // 已执行
new Promise(function (resolve, reject) {// console.log(3); // 已执行
// resolve(); // 已执行
// console.log(4); // 已执行
})
.then(function () {console.log(5);
});
});
此时输入为:5
再读取 ” 工作队列 ” 的回调函数到 ” 执行栈 ”:
setImmediate(function () {console.log(13);
});
此时输入为:13
运行微工作:
无
再读取 ” 工作队列 ” 的回调函数到 ” 执行栈 ”:
// function fn() { // 已执行
// console.log(6); // 已执行
setTimeout(function () {console.log(7);
}, 50);
// }
此时输入为:7
运行微工作:
无
综上,最终的输入程序是:1 8 9 6 11 12 10 2 3 4 5 13 7
看到这里的同学 想必应该曾经把握了 JavaScript 执行机制了吧
心愿大家 2021 心想事成
我是前端魔法师 一名小白 欢送大家点赞 感激大家
参考链接
https://www.cnblogs.com/shcrk…
http://www.ruanyifeng.com/blo…
https://juejin.cn/post/684490…
https://cloud.tencent.com/dev…
https://juejin.cn/post/684490…