异步
JS的异步是通过回调函数实现的,即通过任务队列,在主线程执行完当前的任务栈(所有的同步操作),主线程空闲后轮询任务队列,并将任务队列中的任务(回调函数)取出来执行。"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
虽然JS是单线程的但是浏览器的内核是多线程的,在浏览器的内核中不同的异步操作由不同的浏览器内核模块调度执行,异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含3种 webAPI,分别是 DOM Binding、network、timer模块。
onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。 setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。 ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。
JS中的异步运行机制如下:
1 2 3 4 |
|
ES6 中新增的任务队列(promise)是在事件循环之上的,也就是 ES6 的任务队列比事件循环中的任务(事件)队列优先级更高。
如 Promise ——在执行完任务栈后首先执行的是任务队列中的promise任务。
异步解决的问题
- JS是单线程的,阻塞模式;耗时 效率不高
- 有时候页面并不需要一次性把所有的代码都加载,更多的时候我们是按照某个需求才去加载某段代码的。
使用window.onload包裹
代码,代码在文档全部加载完毕后执行,也就相当于异步。
JS怎么实现异步?
1.利用setTimout实现异步
1 2 3 |
|
setTimeout有些问题--时间不精确,如果想更快的执行这段代码可以使用html5提供的函数。
setImmediate :IE10添加的新功能,专门用于解放ui线程。IE10以下及其他浏览器不支持
1 2 3 | setImmediate(function(){ console.log(1); }) |
requestAnimationFrame :专门用于动画。低级浏览器不支持
1 2 3 |
|
requestAnimationFrame和setTimeout的区别就在于requestAnimationFrame比setTimeout更快执行,因此很多人用requestAnimationFrame来制作动画。
2.动态创建script标签
1 2 3 4 |
|
3.利用script提供的defer/async
1 |
|
defer:当页面加载完毕以后才去执行这段代码。
1 |
|
async:异步执行script代码
4、监听new Image加载状态
通过加载一个image,监听加载状态实现异步。
function asynByImg( callback ) { var img = new Image(); img.onload = img.onerror = img.onreadystatechange = function() { img = img.onload = img.onerror = img.onreadystatechange = null; callback(); } img.src = "data:image/png,"; } asynByImg(function(){ console.log(1); }); console.log(2);
5、监听script加载状态
原理同new Image是一样的,生成一个data:text/javascript的script,并监听其加载状态实现异步。
尽管部分浏览器不支持data:text/javascript格式数据的script,但仍然可以触发其onerror、onreadystatechange事件实现异步。
var asynByScript = (function() { var _document = document, _body = _document.body, _src = "data:text/javascript,", //异步队列 queue = []; return function( callback ) { var script = _document.createElement("script"); script.src = _src; //添加到队列 queue[ queue.length ] = callback; script.onload = script.onerror = script.onreadystatechange = function () { script.onload = script.onerror = script.onreadystatechange = null; _body.removeChild( script ); script = null; //执行并删除队列中的第一个 queue.shift()(); }; _body.appendChild( script ); } })(); asynByScript( function() { console.log(1); } ); console.log(2);
6、Message:
html5;通过监听window.onmessage事件实现,然后postMessage发送消息,触发onmessage事件实现异步
var asynByMessage = (function() { //异步队列 var queue = []; window.addEventListener('message', function (e) { //只响应asynByMessage的召唤 if ( e.data === 'asynByMessage' ) { e.stopPropagation(); if ( queue.length ) { //执行并删除队列中的第一个 queue.shift()(); } } }, true); return function( callback ) { //添加到队列 queue[ queue.length ] = callback; window.postMessage('asynByMessage', '*'); }; })(); asynByMessage(function() { console.log(1); }); console.log(2);
7、Promise:
ES6,具有异步性质
var asynByPromise = (function() { var promise = Promise.resolve({ then : function( callback ) { callback(); } }); return function( callback ) { promise.then(function(){ callback(); }) }; })(); asynByPromise(function() { console.log(1); }); console.log(2);
延时函数不是真正的异步 ------js在执行到延时函数setTimeout时,会触发浏览器的定时器,到设置时间,浏览器再将这个函数放入执行的函数队列,再由JavaScript引擎执行。
AJAX是真正的异步 ------在调用AJAX的时候,浏览器会开辟一个新的线程,去处理这个请求,得到响应后,如果这个请求有回调,会将这个回调再放入事件队列中。再由JavaScript引擎执行