1. 如何理解JS的异步
Javascript运行在浏览器的渲染主线程中,而渲染主线程只有一个,所以Javascript是单线程语言。
而主线程要渲染页面,执行JS等任务,如果使用同步的方式,就极有可能导致主线程阻塞,给用户造成卡死的现象。
所以浏览器采用异步的方式来避免。
2. Javascript为什么不设计成多线程语言?
浏览器中主要实现的是用户与浏览器的交互比如操作DOM,而这些操作必须在单一线程上执行,才能保证页面的安全和稳定。假如是多线程的,那么可能存在多个线程同时访问和修改同一个DOM元素,导致一些不可预料的问题。
3. 单线程的好处
好处是代码简单,易于调试。也可以降低浏览器的内存占用和CPU开销。虽然js是单线程,但是可以通过事件循环机制来处理异步任务,使得程序可以同时执行多个任务,而不阻塞主线程。
4.事件循环基础
javascript是一门单线程语言,分为同步任务和异步任务。
同步任务:是指在主线程上排队执行的任务,只有当前一个任务执行完成,才能继续执行下一个任务。(即不需要等待即可看到执行结果的)
异步任务:是指不进入主线程,而进入“任务队列”的任务;只有等主县城任务全部执行完,“任务队列”的任务才会进入主线程执行。(即需要等待一定的时候才能看到结果的)
异步任务分为:宏任务和微任务。
宏任务由宿主(Node、浏览器)发起。 微任务由JS引擎发起。
new Promise()、console.log()都属于同步任务。
- 宏任务(macrotask):是指在主线程上执行的任务,包括:script(可理解为外层同步代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering
- 微任务:是指在当前宏任务执行结束后立即执行的任务,包括:promise、Object.observe(已废弃,Proxy对象代替)、MutationObserve、process.nextTick(Node.js)
5. 浏览器执行顺序
同步任务---〉微任务---〉宏任务
- 首先执行所有的同步任务,
- 碰到宏任务放到宏任务队列中,
- 碰到微任务放到微任务队列中,
- 当所有的同步代码执行完毕后
- 再将异步微任务从队列中调入主线程执行,
- 微任务执行完后再将异步宏任务从队列调入主线程执行,
- 一直循环直到所有任务完成。
微任务的执行优先级高于宏任务,即微任务中的所有任务都会在下一个宏任务之前执行完毕。 当一个宏任务执行完毕后,会优先执行微任务中的所有任务,直到微任务队列为空,然后再从宏任务队列中取出一个任务,以此类推。
在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
6. 面试题
(1)请说出下面代码的执行顺序
console.log('a');
setTimeout(()=>{
console.log('c');
},0)
new Promise((resolve)=>{
console.log('resolve');
resolve();
}).then(()=>{
console.log('then');
}).then(()=>{
console.log('catch');
})
console.log('b');
正确答案:a,resolve,b,then,catch,c。
(2)在看一道加深印象
console.log('a');
setTimeout(()=>{
console.log('c');
},0)
new Promise((resolve)=>{
console.log('resolve');
resolve();
new Promise((res)=>{
console.log(1);
res();
}).then(()=>{
console.log(2);
})
}).then(()=>{
console.log('then')
})
setTimeout(()=>{
console.log('d');
new Promise((resolve,reject)=>{
console.log('e');
resolve();
}).then(()=>{
console.log('f')
})
})
console.log('b')
正确答案:a,resolve,1,b,2,then,c,d,e,f
(3)看一道包含async的
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2(){
console.log('async2');
}
console.log('script start');
setTimeout(function(){
console.log('setTimeout');
},0)
async1()
new Promise(function(resolve){
console.log('promise1')
resolve()
}).then(()=>{
console.log('promise2');
})
console.log('script end');
正确答案: script start,async1 start,async2,promise1,script end,async1 end,promise2,setTimeout
注意⚠️:
- async和await也属于异步任务中的微任务。
- promise内部遇到resolve和reject调用的时候,会继续执行后面的代码。
- 但是async内部遇到await的时候,只有当await右边的代码执行完成,才会执行后面的代码。此时后面的代码也可以算作微任务。
解析:
async function async1(){
console.log('async1 start'); // 4. 执行过程中这里出现同步代码,所以直接执行【2】
await async2(); // 5. 遇到await,执行await后面的代码即async2函数,await下面这行就放入微任务队列
console.log('async1 end'); // 9.这行代码在微任务队列排第1,先进先出原则所以先执行【6】
}
async function async2(){
console.log('async2'); // 6. 这里是同步代码,直接执行【3】
}
console.log('script start'); // 1.这里是同步代码,直接执行【1】
setTimeout(function(){ // 2. 这里是异步宏任务,放入宏任务队列暂时不执行
console.log('setTimeout'); // 11. 宏任务队列调取执行【8】
},0)
async1() // 3. 这里调用了async1函数,去执行async1
new Promise(function(resolve){
console.log('promise1') // 7. new一个对象是直接执行的,所以这里是【4】
resolve()
}).then(()=>{
console.log('promise2'); // 10.这个在微任务队列排第2,第1已经执行了,现在执行它【7】,发现微任务队列空了,再去执行宏任务
})
console.log('script end'); // 8. 这里是同步代码直接执行【5】,到此同步任务执行完成,去执行微任务
所以执行顺序为:
- script end
- async1 start
- async2
- promise1
- script end
- async1 end
- promise2
- setTimeout