日常开发我们通常都会用到回调函数,promise,setTimeout,我们有没有想过它们在浏览器中是怎样运行的,下面我们通过一个小例子来了解下
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
执行结果:
script start
script end
promise1
promise2
setTimeout
你的答案和浏览器打印出来的结果是一致的吗?
JavaScript是单线程语言,它只有一个调用栈,同一时刻只能做一件事,例如下面的代码
function mutltiply(a,b) {
retrun a * b;
}
function square(n) {
return mutltiply(n,n);
}
function printSqaure(n) {
var squared = square(n);
console.log(squared)
}
printSqaure(4);
main()、printSqaure(4)、square(n)、mutltiply(n,n)依次入栈
mutltiply(n,n)、square(n)、printSqaure(4)、main()依次出栈
最终控制台会输出最终结果:8
浏览器执行JavaScript时,事件循环可以保证这个线程可以独立运行,事件循环不断处理任务队列中的任务。所有任务分为同步任务
和异步任务
两种,同步任务
指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务
指的是,不进入主线程,进入任务队列(Event Queue)的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
所有同步任务都在主线程上执行,形成一个执行栈。
主线程之外存在一个任务队列,只要异步任务有了运行结果,就会在任务队列之中放置一个事件
一旦执行栈中的所有同步任务执行完毕了,系统就会读取任务队列,对应的异步任务会结束等待状态,进入执行栈,开始执行。
主线程不断重复上面的第三步
任务队列中的任务分为宏任务
和微任务
,宏任务一般是整体代码script,setTimeout,setInterval,微任务包含Promise。
每执行完一个宏任务,会执行完所有的微任务,执行完之后才会执行下一个宏任务
既然已经了解到这里了,那么我们可以回头看第一段代码,验证下上面的这些内容
-
遇到console.log(‘script start’);整体代码script作为宏任务压入执行栈,浏览器打印script start
-
遇到setTimeout,回调函数作为宏任务放入任务队列
-
遇到promise,回调函数作为微任务放入任务队列
-
遇到console.log(‘script end’);浏览器打印script end
-
整体代码(一个宏任务)执行完毕,开始执行微任务,浏览器打印promise1
-
promise回调函数返回undefined,下一个回调函数作为微任务放入任务队列
-
执行下一个微任务,浏览器打印promise2
-
执行下一个宏任务setTimeout
最终输出结果为:
script start
script end
promise1
promise2
setTimeout