<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS执行机制title>
head>
<body>
<script>
window.onload = function () {
setTimeout(function () {
console.log('定时器开始啦')
});
new Promise(function (resolve) {
console.log('马上执行for循环啦');
for (var i = 0; i < 10000; i++) {
i == 99 && resolve();
}
}).then(function () {
console.log('执行then函数啦')
});
console.log('代码执行结束');
}
script>
body>
html>
OK!我们来看看这段代码的执行结果是什么?
如果按照JS是按照语句出现的顺序执行
这个理念的话,
那么代码执行的结果应该是:
但是我们把代码放到vs code中运行以后发现,结果是:
看到这里,我们小小的脑袋里有很大的疑惑!
JS不应该是一行一行执行吗?
看来我们真的需要彻底弄明白JavaScript的执行机制了.
JavaScript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但是JavaScript是单线程在这一核心仍未改变.
所以一切JavaScript版的多线程都是用单线程模拟出来的,一切JavaScript多线程都是纸老虎!
单线程通俗来说就是指同一个时间只能做一件事.
我们在DOM操作中创建一个元素,创建成功以后才可以将它添加到我们的节点中,这就充分展示了我们JS单线程的特征.
我们都知道单线程就意味着,所有的任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS执行机制title>
head>
<body>
<script>
window.onload = function () {
console.log(1);
console.log(2);
console.log(3);
/*
执行结果:1、2、3
同步任务,按照顺序一步一步执行
*/
}
script>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS执行机制title>
head>
<body>
<script>
window.onload = function () {
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000)
console.log(3);
/*
执行结果:1、3、2
同步任务,按照顺序一步一步执行
异步任务,放入消息队列中,等待同步任务执行结束,读取消息队列执行
*/
}
script>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS执行机制title>
head>
<body>
<script>
window.onload = function () {
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000)
setTimeout(function () {
console.log(3);
}, 0)
console.log(4);
/*
猜测是:1、4、2、3 但实际上是:1、4、3、2
分析:
同步任务,按照顺序一步一步执行
异步任务,当读取到异步任务的时候,将异步任务放置到Event table(事件表格)
中,当满足某种条件或者说指定事情完成了(这里的是时间分别是达到了0ms和1000ms)当指定
事件完成了才从Event table中注册到Event Queue(事件队列),当同步事件完成了,便从
Event Queue中读取事件执行。(因为3的事情先完成了,所以先从Event table中注册到
Event Queue中,所以先执行的是3而不是在前面的2)
*/
}
script>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS执行机制title>
head>
<body>
<script>
window.onload = function () {
console.log(1);
setTimeout(function () {
console.log(2)
}, 1000);
new Promise(function (resolve) {
console.log(3);
resolve();
}
).then(function () {
console.log(4)
});
console.log(5);
//以同步异步的方式来判断的结果应该是:1、3、5、2、4
//但是事实上结果是:1、3、5、4、2
//为什么是这样呢?
//因为以同步异步的方式来解释执行机制是不准确的,更加准确的方式是宏任务和微任务:
//因此执行机制便为:执行宏任务 ===> 执行微任务 ===> 执行另一个宏任务 ===> 不断循环
//即:在一个事件循环中,执行第一个宏任务,宏任务执行结束,执行当前事件循环中的微任务,
// 执行完毕之后进入下一个事件循环中,或者说执行下一个宏任务
}
script>
body>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS执行机制title>
head>
<body>
<script>
window.onload = function () {
console.log('1');
setTimeout(function () {
console.log('2');
process.nextTick(function () {
console.log('3');
})
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5')
})
})
process.nextTick(function () {
console.log('6');
})
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8')
})
setTimeout(function () {
console.log('9');
process.nextTick(function () {
console.log('10');
})
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12')
})
})
/*
1、 第一轮事件循环流程分析如下:
整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
宏任务Event Queue 微任务Event Queue
setTimeout1 process1
setTimeout2 then1
上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
我们发现了process1和then1两个微任务。
执行process1,输出6。
执行then1,输出8。
好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。
2、 那么第二轮时间循环从setTimeout1宏任务开始:
首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,
记为process2。new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
宏任务Event Queue 微任务Event Queue
setTimeout2 process2
then2
第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。
输出3。
输出5。
第二轮事件循环结束,第二轮输出2,4,3,5。
3、 第三轮事件循环开始,此时只剩setTimeout2了,执行。
直接输出9。
将process.nextTick()分发到微任务Event Queue中。记为process3。
直接执行new Promise,输出11。
将then分发到微任务Event Queue中,记为then3。
宏任务Event Queue 微任务Event Queue
process3
then3
第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
输出10。
输出12。
第三轮事件循环结束,第三轮输出9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
*/
}
script>
body>
html>
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。
所以,这个新标准并没有改变JavaScript单线程的本质。
所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。
但是,由于存在后文提到的"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
读取到一个异步任务,首先是将异步任务放进事件表格(Event table)中,当放进事件表格中的异步任务完成某种事情或者说达成某些条件(如setTimeout事件到了,鼠标点击了,数据文件获取到了)之后,才将这些异步任务推入事件队列(Event Queue)中,这时候的异步任务才是执行栈中空闲的时候才能读取到的异步任务。
HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。
另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。
需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
参考网址
https://www.cnblogs.com/shcrk/p/9325779.html
https://juejin.im/post/59e85eebf265da430d571f89
https://www.cnblogs.com/MasterYao/p/5563725.html
https://blog.csdn.net/highboys/article/details/79110116