首先,我想说一下Javascript的装载和执行。
通常来说,浏览器对于Javascript的运行有两大特性:
1)载入后马上执行,
2)执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载)**。于是,如果有多个js文件被引入,那么对于浏览器来说,这些js文件被被串行地载入,并依次执行。
因为javascript可能会来操作HTML文档的DOM树,所以,浏览器一般都不会像并行下载css文件并行下载js文件,因为这是js文件的特殊性造成的。
所以,如果你的javascript想操作后面的DOM元素,基本上来说,浏览器都会报错说对象找不到。因为Javascript执行时,后面的HTML被阻塞住了,DOM树时还没有后面的DOM结点。所以程序也就报错了。
有没有办法实现只加载不立即执行,后续按需执行呢?
http://www.linjunlong.com/p/1156.html 这里有一种方案。
下面说说具体执行:
-
栈和队列
在上图中,调用栈中遇到DOM操作、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理,webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现。等到这些模块处理完这些操作的时候将回调函数放入任务队列中,之后等栈中的task执行完之后再去执行任务队列之中的回调函数
可以看出:
所有的代码都要通过函数调用栈中调用执行。
当遇到前文中提到的APIs的时候,会交给浏览器内核的其他模块进行处理。
任务队列中存放的是回调函数。
等到调用栈中的task执行完之后再回去执行任务队列之中的task。
- 整个事件循环机制包含两种事件队列
(function test() {
/*开始执行本次macro-task*/
setTimeout(function() {console.log(4)}, 0);----作为一个macro-task,将其回调函数放入自己的队列之中。
new Promise(function executor(resolve) {
console.log(1);---- 立即执行
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(2);---- 立即执行
}).then(function() {
console.log(5);---micro task queue
});
console.log(3);---- 立即执行
})()
macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver
事件循环的顺序是从script开始第一次循环,随后全局上下文进入函数调用栈,碰到macro-task就将其交给处理它的模块处理完之后将回调函数放进macro-task的队列之中,碰到micro-task也是将其回调函数放进micro-task的队列之中。直到函数调用栈清空只剩全局执行上下文,然后开始执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次执行macro-task中的一个任务队列,执行完之后再执行所有的micro-task,就这样一直循环。
总结
不同的任务会放进不同的任务队列之中。
先执行macro-task,等到函数调用栈清空之后再执行所有在队列之中的micro-task。
等到所有micro-task执行完之后再从macro-task中的一个任务队列开始执行,就这样一直循环。
当有多个macro-task(micro-task)队列时,事件循环的顺序是按上文macro-task(micro-task)的分类中书写的顺序执行的。