JavaScript概念辨析
如何理解宏任务、微任务?
异步处理
JS 引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。
如何理解宏任务、微任务?
JavaScript代码执行顺序:
- 先执行同步代码
- 遇到异步宏任务则将放入宏任务队列中,遇到异步微任务则将放入微任务队列中
- 当所有同步代码执行完毕后
- 将异步微任务从队列中调入主线程执行
- 再将异步宏任务从队列中调入主线程执行(宏任务执行中会产生微任务,这些微任务会在下一个宏任务执行前就完成掉)
- 循环往复…
结论:在异步任务中:微任务先于宏任务
运行机制
在事件循环中,每进行一次循环操作称为 tick,关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
宏任务
(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程如下:
(macro)task->渲染->(macro)task->…
宏任务具体包括:
script(整体代码)
计时器结束的回调,setTimeout、setInterval
事件回调、http 回调等绝大部分异步函数进入宏队列
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的
微任务
在当前 task 执行结束后立即执行的任务。在当前task任务后,下一个task之前,在渲染之前。
在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕
具体包括:
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)
案例
setTimeout(_ => console.log(4))
new Promise(resolve => {
resolve()
console.log(1)
}).then(_ => {
console.log(3)
})
console.log(2)
- 整体script作为第一个宏任务进入主线程,遇到setTimeout入栈处理,发现是异步函数(宏任务),出栈,移交给Web API处理,0秒等待后,将回调函数加到宏任务队列尾部;
- 遇到new Promise,入栈处理,发现是同步任务,直接执行,console输出1;
- 遇到then,入栈处理,发现是异步函数(微任务),出栈,移交给Web API处理,将回调函数加入微任务队列尾部;
- 遇到console.log(2),入栈处理,同步任务,直接console输出2, 出栈;
- 栈已清空,检查微任务队列;
- 取出第一个回调函数,入栈处理,发现是同步任务,直接console输出3, 出栈;
- 继续从取微任务队列中取下一个,发现微任务队列已清空,结束第一轮事件循环;
- 从宏任务队列中取出第一个宏任务,入栈处理,发现是同步任务,直接console输出4;
所以,最终输出结果为:1 > 2 > 3 > 4
JavaScript异步解决方案
- 回调函数,解决同步问题,但是带来回调地狱
- promise,解决回调地狱,但是无法取消promise,错误需要用回调函数来捕获
- generator,控制函数的执行
- async/await,代码清晰,不用写一堆then链
代理和反射
最后一遍说 bind、call、apply
区别一:call、apply是立即执行、bind是返回一个新的函数
bind 返回的是一个新的函数,你必须调用它才会被执行
obj.myFun.call(db); // 德玛年龄 99
obj.myFun.apply(db); // 德玛年龄 99
obj.myFun.bind(db)(); // 德玛年龄 99
区别二:
call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别
call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔
apply 的所有参数都必须放在一个数组里面传进去
bind 除了返回是函数以外,它 的参数和 call 一样
obj.myFun.call(db,'成都','上海'); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.apply(db,['成都','上海']); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.bind(db,'成都','上海')(); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])(); // 德玛 年龄 99 来自 成都, 上海去往 undefined