We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
我们都知道JS引擎是单线程、同步执行的了,但是对于I/O业务密集型的前端,没有异步编程是不行的,会非常影响用户的体验。
异步模块并不属于V8 引擎范围,它是浏览器内核提供的Web API,供开发者使用的,用于提供用户体验的模块,比如经常使用到的setTimeout、XMLHttpRequest、Fetch、Promise等等API。
Web API
举个例子
同步就是烧开水,需要自己去轮询(每隔一段时间去看看水开了没),异步就是水开了,然后水壶会通知你水已经开了,你可以回来处理这些开水了。 同步和异步是相对于操作结果来说,是自动去询问操作有结果了么,还是等待操作结束了带着结果告知予你。
阻塞就是说在煮水的过程中,你不可以去干其他的事情,非阻塞就是在同样的情况下,可以同时去干其他的事情。 阻塞和非阻塞是相对于线程是否被阻塞,是等待操作结果返回,还是接着往下执行代码。
所以,JS的异步非阻塞场景就是,主线程遇到一个需要长时间等待结果操作,可以先不等它返回结果,继续执行其他操作;等待该操作产生结果了,会带着结果来通知主线程。那么JS是怎么实现这样的效果呢?答案就是异步编程和事件循环。
对于主线程需要异步的操作(长时间操作),都交给异步处理模块(通常是另一条线程),等到处理结果出来了,由异步处理模块把处理结果和回调传回给主线程的任务队列(task-queue),主线程会不停地去任务队列取回调来执行,这就是事件循环。
这是定时器线程提供的两个api,两者的常用api如下:
setTimeout(func,[delay,param1,param2,····]); setInterval(func, delay[,param1, param2, ...]);
setTimeout方法会让定时器线程在过了delay时间后,把回调(func)放入JS引擎线程的任务队列中,很多博客都会提到setTimeout的一些缺点,就是这个api不能保证在过了delay时间后执行,其实原理很简单,我们知道JS引擎会在主线程执行完毕之后才会到任务队列中提取回调到主线程中执行,所以定时器线程能保证delay时间之后把回调放入任务队列,但是不能保证这个时候主线程是空闲的。
setInterval方法,在nodejs环境和浏览器环境下是表现不一样的。
setInterval的回调在delay时间后,被放入JS引擎线程的任务队列中,等待被JS引擎线程放入执行上下文栈中执行,被执行完毕之后,会调用自身一次,也就是请求定时器线程在delay时间后再次把回调放入任务队列中。用setTimeout来表示这个逻辑的话:
setTimeout(function(){ // some code setTimeout(arguments.callee, interval); }, interval);
每隔delay时间后,定时器线程会把回调放入任务队列中,如果任务队列中已经存在回调还没被执行的话会被覆盖掉。换句话说,如果你想要6秒内执行3次(delay = 2000),但是回调执行时间太长,比如执行了4秒,那么这段时间内会被插入两个回调,那么只有第二个回调会被执行。
(function inter(num) { setInterval(() => { var t = +new Date(); ++num; console.log(num, new Date()); while (+new Date() - t < 1000); // 阻塞一秒 }, 2000) // 间隔两秒 })(0) NodeJs output(回调间隔3秒): 1 2018-05-09T01:55:54.841Z 2 2018-05-09T01:55:57.842Z 3 2018-05-09T01:56:00.842Z 4 2018-05-09T01:56:03.842Z 5 2018-05-09T01:56:06.842Z 浏览器 output(回调间隔2秒): 1 Wed May 09 2018 09:56:28 GMT+0800 (中国标准时间) 2 Wed May 09 2018 09:56:30 GMT+0800 (中国标准时间) 3 Wed May 09 2018 09:56:32 GMT+0800 (中国标准时间) 4 Wed May 09 2018 09:56:34 GMT+0800 (中国标准时间) 5 Wed May 09 2018 09:56:36 GMT+0800 (中国标准时间)
任务队列分宏任务队列(macro-task-queue)和微任务队列(micro-task-queue),如下:
Object.observe(obj, callback[, acceptList])已废弃,可以使用Proxy对象代替。
MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。
一个网页有若干个宏任务队列,根据任务源来分类,大概有如下任务源:
每一个宏任务都有一个微任务队列,在宏任务结束之前会把微任务队列里的任务执行完毕。
setTimeout(()=>{ var demoDiv = document.getElementById('test') demoDiv.innerText = 'hello world'; // 变更 DOM var t = +new Date(); while(+new Date() - t < 3000) ; // 阻塞3秒,才会把变更的dom穿给GUI线程 })
过程大概如下图:
这是浏览器专门为动画提供的API,用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
举个例子 如果有一个需求,一秒钟往ul标签里插入1000个li标签,可以借助这个api,每帧(1秒有60帧)插入17个li标签,那么用户会觉得画面很顺畅
简单介绍:Vue对象的数据变更并不是执行了就会立马更新到DOM上,而是把所有变更存储起来等待事件循环来处理,为了得到立马更新的DOM,需要调用nextTick这个API。
<div id="example">{{message}}</div> new Vue({ el: '#example', data: { message: '123' } methods: { updateMessage: () => { vm.message = 'new message' // 更改数据 console.log(vm.$el.textContent) // '123',因为还未更新到dom属性 console.log(vm.message) // 'new message' } } })
nextTicke源码(2.5.17版本)在这里
大致过程是这样的:优先使用(Promise > setImmediate > MessageChannel > setTimeout)来作为它的实现方式,用一个数组callbacks来存储一个执行上下文中多次调用的nextTick的回调,经过特殊处理后,并不是每个nextTick都产生一个micro-task(或者macro-task),而是在一个micro-task(或者macro-task)中把callbacks全部循环执行了。
callbacks
VueJs官网关于异步更新队列,有下面一句话
Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
可以看得出来,如果异步更新队列使用的是micro-task方式更新的,那么nextTick也会是同样的micro-task方式实现的,所以只需要保证异步更新队列先放入micro-task-queue,nextTick后放入的话,nextTick里的回调是可以正确取到dom属性的值。(如果是使用macro-task的方式,那么nextTick就会被安排到下一次tick,也可以正确取到dom属性的值)
前面说过Promise是一个异步处理框架。 在JS发展史里,最早广泛使用Promise的是jQuery.Deffered()。Promise/A+标准规定了一系列API,先是NodeJs有了开源的Promise库,后来ES6直接整合这个标准,NodeJs支持ES6的时候也不需要开源的Promise库了。
jQuery.Deffered()
Promise.all() Promise.prototype.catch() Promise.prototype.finally() Promise.prototype.then() Promise.race() Promise.reject() Promise.resolve()
作为一个异步处理框架,提供API给前端工程师使用,但是内部实现不详。
以ES6的Promise为参考,使用timeout和ES6的语法(可选)实现。 由于篇幅过多,我把它独立成一个项目 —— 用代码讲述Promise原理——每个人都应该有自己的Promise
我曾经想过找到这个api,然后每个前端工程师都可以写自己的异步框架了。 想象中的这个api应该是这样的:
push-task-into-queue(callback, type) // 其中的callback是放入队列的回调,type是队列的类型:macro-task还是micro-task
但是一些网友告诉我,这个api是浏览器内部实现的,不会公开的。略感失望。
JS相关的异步编程和事件队列涉及到的东西真不少,这篇博客有误的地方请指正。
VueJs的异步更新队列 Promise API Promises/A+ 规范 The Node.js Event Loop, Timers, and process.nextTick() 深入理解js事件循环机制(Node.js篇) 深入理解js事件循环机制(浏览器篇) https://www.cnblogs.com/George1994/p/6702084.html https://juejin.im/post/599ff3d5f265da24843e6276 https://segmentfault.com/a/1190000011198232 https://www.cnblogs.com/xiaohuochai/p/5777186.html https://github.com/aooy/blog/issues/5 https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
The text was updated successfully, but these errors were encountered:
No branches or pull requests
概述
我们都知道JS引擎是单线程、同步执行的了,但是对于I/O业务密集型的前端,没有异步编程是不行的,会非常影响用户的体验。
异步模块并不属于V8 引擎范围,它是浏览器内核提供的
Web API
,供开发者使用的,用于提供用户体验的模块,比如经常使用到的setTimeout、XMLHttpRequest、Fetch、Promise等等API。定义
1. 同步和异步、阻塞与非阻塞的关联和区别
举个例子
所以,JS的异步非阻塞场景就是,主线程遇到一个需要长时间等待结果操作,可以先不等它返回结果,继续执行其他操作;等待该操作产生结果了,会带着结果来通知主线程。那么JS是怎么实现这样的效果呢?答案就是异步编程和事件循环。
2. 浓缩成一段话
对于主线程需要异步的操作(长时间操作),都交给异步处理模块(通常是另一条线程),等到处理结果出来了,由异步处理模块把处理结果和回调传回给主线程的任务队列(task-queue),主线程会不停地去任务队列取回调来执行,这就是事件循环。
这篇博客涉及到的概念
1. setTimeout和setInterval
这是定时器线程提供的两个api,两者的常用api如下:
A. setTimeout方法
setTimeout方法会让定时器线程在过了delay时间后,把回调(func)放入JS引擎线程的任务队列中,很多博客都会提到setTimeout的一些缺点,就是这个api不能保证在过了delay时间后执行,其实原理很简单,我们知道JS引擎会在主线程执行完毕之后才会到任务队列中提取回调到主线程中执行,所以定时器线程能保证delay时间之后把回调放入任务队列,但是不能保证这个时候主线程是空闲的。
B. setInterval方法
setInterval方法,在nodejs环境和浏览器环境下是表现不一样的。
1. NodeJs环境下
2. 浏览器环境下
3. 测试上面的两个结论代码:
2. 任务队列
任务队列分宏任务队列(macro-task-queue)和微任务队列(micro-task-queue),如下:
一个网页有若干个宏任务队列,根据任务源来分类,大概有如下任务源:
每一个宏任务都有一个微任务队列,在宏任务结束之前会把微任务队列里的任务执行完毕。
3. 事件循环机制
1. 事件循环的步骤
2. 网页渲染时机
过程大概如下图:

3. requestAnimationFrame
这是浏览器专门为动画提供的API,用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
4. 研究VueJS的nextTick源码
简单介绍:Vue对象的数据变更并不是执行了就会立马更新到DOM上,而是把所有变更存储起来等待事件循环来处理,为了得到立马更新的DOM,需要调用nextTick这个API。
1. 需要着重讲一下VueJs中数据的三种状态
2. nextTicke源码
nextTicke源码(2.5.17版本)在这里
大致过程是这样的:优先使用(Promise > setImmediate > MessageChannel > setTimeout)来作为它的实现方式,用一个数组
callbacks
来存储一个执行上下文中多次调用的nextTick的回调,经过特殊处理后,并不是每个nextTick都产生一个micro-task(或者macro-task),而是在一个micro-task(或者macro-task)中把callbacks
全部循环执行了。3. 如何保证nextTick里能确切拿到更新后的dom属性呢?
VueJs官网关于异步更新队列,有下面一句话
可以看得出来,如果异步更新队列使用的是micro-task方式更新的,那么nextTick也会是同样的micro-task方式实现的,所以只需要保证异步更新队列先放入micro-task-queue,nextTick后放入的话,nextTick里的回调是可以正确取到dom属性的值。(如果是使用macro-task的方式,那么nextTick就会被安排到下一次tick,也可以正确取到dom属性的值)
5. Promise异步处理
1. 介绍promise
前面说过Promise是一个异步处理框架。
在JS发展史里,最早广泛使用Promise的是
jQuery.Deffered()
。Promise/A+标准规定了一系列API,先是NodeJs有了开源的Promise库,后来ES6直接整合这个标准,NodeJs支持ES6的时候也不需要开源的Promise库了。2. Promise的API
3. ES6的Promise
作为一个异步处理框架,提供API给前端工程师使用,但是内部实现不详。
4. 模仿实现ES6的Promise
以ES6的Promise为参考,使用timeout和ES6的语法(可选)实现。
由于篇幅过多,我把它独立成一个项目 —— 用代码讲述Promise原理——每个人都应该有自己的Promise
6. 寻找跟任务队列交互的API
我曾经想过找到这个api,然后每个前端工程师都可以写自己的异步框架了。
想象中的这个api应该是这样的:
但是一些网友告诉我,这个api是浏览器内部实现的,不会公开的。略感失望。
写在最后
JS相关的异步编程和事件队列涉及到的东西真不少,这篇博客有误的地方请指正。
参考链接
VueJs的异步更新队列
Promise API
Promises/A+ 规范
The Node.js Event Loop, Timers, and process.nextTick()
深入理解js事件循环机制(Node.js篇)
深入理解js事件循环机制(浏览器篇)
https://www.cnblogs.com/George1994/p/6702084.html
https://juejin.im/post/599ff3d5f265da24843e6276
https://segmentfault.com/a/1190000011198232
https://www.cnblogs.com/xiaohuochai/p/5777186.html
https://github.com/aooy/blog/issues/5
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
The text was updated successfully, but these errors were encountered: