线程
JavaScript特点就是单线程,理解是,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?
现在我们假设,JavaScript同时有多个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,注定JavaScript就是单线程处理。
但是单线程有不好地方,单线程就意味着,所有任务需要排队进行处理,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就一直等着。
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
任务队列
同步:如果在函数返回的时候,调用者就能够得到预期结果,那么这个函数就是同步的。
例如:console.log('Hi');
异步:如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在通过一定的手段得到,那么这个函数就是异步的。
例如通过Ajax获取
任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。有没有对如何通知主线程存在疑惑???通知主线程的意思就是只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。利用主线程在队列中读取的事件来判断是哪个异步任务完成了。然后把它添加到主线程中来,执行他!
总结一下:
JS的执行机制就是一个主线程 + 一个任务队列。同步任务就是放在主线程上执行的任务,异步任务就是放在任务队列的任务。所有的同步任务都在主线程执行,这构成了一个执行栈,异步任务有了运行结果会在任务队列中放置一个事件,比如定时2秒,到2秒后才能放进任务队列(callback放进任务队列,而不是setTimeout函数放进队列)。脚本运行时,先依次运行执行栈,然后从队列中提取事件来运行任务队列中的任务,这个过程是不断重复的。所以叫事件循环(Event Loop)。
JavaScript的运行机制
只要主线程空了,就会去读取"任务队列",这就是我们说的JavaScript的运行机制。
事件和回调函数
"任务队列"是一个事件的队列,IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"主线程执行"了。主线程读取"任务队列",就是读取里面有哪些事件。
"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,如果存在"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
Event Loop
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
定时器
"任务队列"还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做"定时器"(timer)功能,也就是定时执行的代码。
它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。
setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。
开始案例分析:
console.log('-----start-----');
setTimeout(function() {console.log('hello');}, 200);
setTimeout(function() {console.log('world');}, 100);
console.log('-----end-------');
console.log(1);
setTimeout(function() {console.log(2);}, 300);//timer1
setTimeout(function() {console.log(3);}, 400);//timer2
for (var i = 0; i < 10000; i++) {
console.log(4);//大约需要4000ms的时间
}
setTimeout(function() {console.log(5);}, 100);//timer3
console.log(1);
setTimeout(function() {console.log(2);}, 400);
setTimeout(function() {console.log(3);}, 300);
for (var i = 0; i < 10000; i++) {
console.log(4)
}
setTimeout(function() {console.log(5);}, 100);
console.log('--------end---------');
setTimeout(function() {console.log('我先定时的,我400ms');}, 400);
var start = new Date();
for (var i = 0; i < 5000; i++) {
console.log('这里模拟了5000次循环的耗时操作');
}
var end = new Date();
console.log('阻塞时长:' + Number(end - start) + '毫秒');
setTimeout(function() {console.log('我后定时的,我300ms');}, 300);
setTimeout(function() {console.log('我先定时的,我400ms');}, 400);
var start = new Date();
for (var i = 0; i < 500; i++) {
console.log('这里模拟了500次循环的耗时操作');
}
var end = new Date();
console.log('阻塞时长:' + Number(end - start) + '毫秒');
setTimeout(function() {console.log('我后定时的,我300ms');}, 300);
额外补充:
虽然JS是单线程的但是浏览器的内核是多线程的,在浏览器的内核中不同的异步操作由不同的浏览器内核模块调度执行,异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含上图中的3种 webAPI,分别是 DOM Binding、network、timer模块
onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)