JS面试题
DOM事件和event loop
-
都是回调函数,但是触发的方式和时机不同,一个是设置固定时间,一个是用户点击后触发
-
JS是单线程的
-
异步(setTimeout(定时),ajax(网络请求返回时)等)使用回调,基于event loop
-
DOM(事件触发时,如点击,鼠标滚动,键盘事件) 事件也是基于回调,基于event loop,但dom事件不是异步执行细节:当代码运行到
$('#btn1').click()
(这些代码浏览器立刻执行的),同样会把回到函数function(e){log("")}
它放到webAPIs里,等到同步代码执行完毕,尝试渲染dom(Event Loop开始轮询),用户触发点击事件时,会把回调函数(function(e){log("")}
)放到callback Queue中,然后Event Loop再把回调函数放到调用栈callStack中 执行
Promise有哪三种状态
-
三种状态
- pending resolved rejected
- pending=>resolved 或 pending=>rejected
- 变化不可逆
-
状态的表现和变化
-
pending状态promise,不会触发then和catch
-
resolved状态promise,会触发后续的then回调函数
-
rejected 状态promise,会触发后续的catch回调函数
const p1 = new Promise((resolve,reject)=>{ }) console.log('p1',p1);//pending const p2 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve() }) }) console.log('p2',p2);//pending 一开始打印时 setTimeout(()=>console.log('p2-setTimeout',p2))//resolved const p3 = new Promise((resolve,reject)=>{ setTimeout(()=>{ reject() }) }) console.log('p3',p3);//pending 一开始打印时 setTimeout(()=>console.log('p3-setTimeout',p3))//rejected
const p1 = Promise.resolve(100)//返回resolved状态的promise console.log('p1',p1); p1.then(data=>{ console.log('data',data); }).catch(err=>{ console.error('err',err); }) const p2 = Promise.reject('err')//rejected // console.log('p2',p2); p2.then(data=>{ console.log('data2',data); }).catch(err=>{ console.error('err2',err); })
-
-
then和catch对状态的影响
-
then
和catch
改变状态- then没有报错返回resolved状态的promise,接着会触发后边的then回调,里面有报错则返回rejected状态的promise,
会触发后边的catch回调 - catch没有报错返回resolved状态的promise,接着会触发后边的then回调,里面有报错则返回rejected状态的promise,
会触发后边的catch回调
- then没有报错返回resolved状态的promise,接着会触发后边的then回调,里面有报错则返回rejected状态的promise,
-
总结
- 三种状态,状态的表现和变化
then
和catch
对状态的影响(重要)then
和catch
的链式调用(常考)
async/await
- 异步回调
callback hell
Promise then catch
链式调用,但是也是基于回调函数Async/await
是同步语法,彻底消灭回调函数
async/await和Promise的关系
-
async/await
是消灭异步回调的终极武器 -
但是和
Promise
并不互斥 -
反而两者相辅相成
-
执行
async
函数,反回的是Promise
对象 -
await
相当于Promise
的then
-
try...catch
可捕获异常,代替了Promise
的catch
async function fn1() { // return 100;//封装成Promise对象返回 相当于return Promise.resolve(100) return Promise.resolve(200); }; // const res1 = fn1(); //执行async函数,返回的是一个Promise对象 // console.log('res1', res1); // res1.then(data => { // console.log('data', data); // }) // //await 后边跟promise // (async function(){ // const p1 = Promise.resolve(300); // const data = await p1;//如果await后边跟的是promise,那么await 相当于 Promise 的then // console.log('data',data); // })(); // //await 后边跟数值 // (async function(){ // const data1 = await 400;//如果await后边跟的是非Promise的值,那么会把它封装成Promise对象,相当于 await Promise.resolve(400) // console.log('data1',data1); // })(); // //await 后边跟async函数 // (async function(){ // const data2 = await fn1(); // console.log('data2',data2); // })(); // (async function(){ // const p4 = Promise.reject('err1');//rejected // try { // const res = await p4; // console.log(res); // } catch (ex) { // console.error(ex);//try...catch 相当于promise的catch // } // })() (async function(){ const p4 = Promise.reject('err1');//rejected const res = await p4; //await 相当于 then ,但是这里的p4是rejected状态的promise所以不会执行到await,这种情况就要用到上边的try...catch console.log('res',res); })()
异步的本质
-
async/await
是消灭异步回调的终极武器 -
JS还是单线程,还得是有异步,还得是基于event loop
-
async/await 只是语法糖,但是这颗糖真香
//面试题 async function async1(){ console.log('async1 start'); await async2(); //undefined //await的后面,都可以看做是callback里的内容,即异步,所以暂时不会打印。函数体执行完,接着会执行后边的script end,此时同步代码迎执行完,最后打印saync1 end //类似,event loop ,setTimeout(cb1) //setTimeout(function(){console.log('async1 end')})或者Promise.resolve().then(()=>{console.log('async1 end')}) console.log('async1 end'); await async3(); console.log('async1 end 2'); } async function async2(){ console.log('async2'); } async function async3(){ console.log('async3'); } console.log('script start'); async1(); console.log('script end'); //1,script start 2.async1 start 3.async2 4.script end 5.async1 end 6,async3 7async1 end 2
for..of
for...in
(以及forEach for
)是常规的同步遍历for...of
常用于异步的遍历
function muti(num){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(num*num)
},1000)
})
}
const nums = [1,2,3];
//同步循环
//如果使用foreach遍历处理异步代码不会等待异步代码的执行,一次输出所有异步结果
// nums.forEach(async(i)=>{
// const res = await muti(i);
// console.log(res);
// })
//异步循环
//果使用for-of可以使异步按先后顺序执行
//运用async/await方法,当异步函数里执行resolve时,await接收到成功的指示,然后进行下一步
(async function(){
for(let i of nums){
const res = await muti(i);
console.log(res);
}
})();
async/await总结
- async/await 只是语法糖,但是这颗糖真香
- async/await和Promise的关系
- for...of的使用
宏任务macroTask和微任务microTask
- 什么是宏任务,什么是微任务
- 宏任务:
setTimeout,setInterval,Ajax,DOM
事件- dom渲染后触发,如setTimeout
- 微任务:
Promise async/await
- dom渲染前触发,如promise
- 微任务执行时机比宏任务要早(记住)
- 宏任务:
- event loop 和 DOM渲染
- 宏任务和微任务的区别
event loop 和DOM渲染
- 再次回顾一遍eventloop的过程
- JS是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一些时机提供DOM渲染
回顾eventloop过程(增加dom渲染时机)
当同步代码执行完毕时,callstack会处于空闲状态(空的),这是浏览器会尝试dom渲染(如果代码中有操作),然后再第一次启动eventloop,如果callback queue中有回调函数,那么会把它放到callstack中去执行,执行完毕再尝试dom渲染,再启动eventloop.所以,每次eventloop轮询或者callstack执行完毕后都会尝试dom渲染
-
每次Call Stack清空(即每次轮询结束),即同步任务完成
-
都是dom重新渲染的机会,dom结构如有改变(修改dom的代码)则重新渲染
-
然后再去触发下一次eventloop
//证实callstack结束 之后会渲染dom const $p1 = $('<p>一段文字</p>'); const $p2 = $('<p>一段文字</p>'); const $p3 = $('<p>一段文字</p>'); $('#container').append($p1).append($p2).append($p3); console.log('length',$('#container').children().length); alert('本次 call stack 结束,dom 结构已更新,但是尚未触发渲染'); //alert 会阻断js执行,也会阻断dom渲染,便于查看效果
//证实微任务是dom渲染前触发的,而宏任务是在dom渲染后触发的 const $p1 = $('<p>一段文字</p>'); const $p2 = $('<p>一段文字</p>'); const $p3 = $('<p>一段文字</p>'); $('#container').append($p1).append($p2).append($p3); //微任务:dom渲染前触发 Promise.resolve().then(()=>{ console.log('length1',$('#container').children().length);//3 alert('Promise then');//dom渲染了吗? --no }) //宏任务:dom渲染后触发 setTimeout(() => { console.log('length2',$('#container').children().length);//3 alert('setTimeout');//dom渲染了吗? --yes });
从eventloop角度解释,为什么微任务执行的时机更早(根本区别)
-
微任务是ES6语法规定的
-
宏任务是由浏览器规定的
-
Micro tast queue:微任务队列
-
callstack中执行到微任务代码时,会等待时机,然后直接把它放到微任务队列(Micro tast queue)
-
callstack清空之后->执行微任务->dom渲染->触发eventloop->把回调函数放到callstack执行宏任务
//微任务宏任务面试真题 console.log(100) setTimeout(() => { console.log(200) }) Promise.resolve().then(() => { console.log(300) }) console.log(400) // 100 400 300 200