说道异步问题,可能最常见的就是一下几种
1、使用 callback 回调函数
2、使用 事件监听机制
3、使用 发布/订阅模式
4、使用 Promise 异步
5、使用 async/await 实现异步
剩下的还有:
6、envent loop 事件队列实现异步操作
7、defer 通过给脚本文件添加 defer 属性,实现异步
callback
callback应该是最熟悉的异步操作了,毕竟在没有这么丰富的异步方法之前,我们都是在回调地狱里面苦苦挣扎的。callback其实就是将某个 函数作为参数,传递到另外一个函数中,然后在另外一个函数中去执行
function test1(test2) { console.log("这是test1"); test2(); } function test2() { console.log("这是test2"); test3(); } function test3() { console.log("这是test3"); } test1()这样看起来,异步的代码变成了同步的写法,但是,实际上还是走的异步的流程,只是在视觉和写法上体改了很多,避免了所有代码全部嵌套在一起,过于臃肿,不易阅读。
同时,回调函数的错误捕获抛出也是需要掌握的。类似下面这种,只要碰到错误,就在函数体内部 抛出错误 throw err
const fs = require('fs') fs.readFile('file.txt', (err, data) => { if (err) { throw err } console.log(data) })事件监听
js代码的执行并不一定是按照代码的排列顺序,我们可以通过事件的触发来控制代码的执行顺序,也就达到了异步的效果。
function test4(params) { console.log('监听到了我就执行,没有监听到就执行其他代码'); }发布/订阅
这个模式是从观察者模式上派生出来的,但是现在可以说已经足够强大了,所以已经成为了一个独立的模式,虽然24设计模式中暂时还没有这个模式,使用过Vue框架的应该不会陌生, Vue的双向数据绑定也就是用到了 发布者 模式。
利用一个消息中心,发布者发布一个消息给消息中心,订阅者在消息中心中订阅该消息。每当数据或状态改变时,都会向消息中心发布消息,订阅者并不关心你的消息是谁发的,我只是在消息中心拿到我的订阅。
这就像微博关注了一个人,他发了动态之后,微博会给我推送消息,而我和这个发微博的人本身是没有关联的。也就是jq里面的on,js的 addEventListener
发布-订阅模式有一个消息中心,但是观察者模式没有消息中心,而是属性改变会通知属性对应的 dep实例 去通知数组里面的所有 watcher,watcher之后再通过 get和set改变数据
//订阅done事件 $('#app').on('done',function(data){ console.log(data) }) //发布事件 $('#app').trigger('done,'haha')Promise
强大的 Promise 解决了回调地狱,至于 Promise 强大在哪里,使用 Promise 的好处在哪里,都可以在这篇 博文里面看一看
ES6---new Promise()使用方法
async/await
上面的 Promise 是 es6 的薪特性,但是js的规范现在都到 es10 了,所以,新特性也是在不断增加。
async/await 就是在 es7 新增的 用于解决异步的 新特性
async函数返回的是一个 Promise 对象,可以使用 then 方法添加回调函数,async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 1000); }); async function waitFn() { let a = await p1; // await命令后面可以是 Promise 对象和原始类型的值,如果使原始类型最终也会返回为Promise对象 let b = await p2; return a + b } // async函数的返回值是 Promise 对象, 可以用then方法指定下一步的操作 waitFn().then(result => { console.log(result); // 2s后输出3 });envent loop
参考 详解JavaScript中的Event Loop(事件循环)机制 个人觉得这篇文章将事件循环,执行栈,宏任务和微任务的执行顺序讲解的非常清楚,建议多读几遍。
defer
只有 Internet Explorer 支持 defer 属性。defer 属性规定是否对脚本执行进行延迟,直到页面加载为止。
除了这个浏览器限制之外,还有另外的限制:如果js脚本不会改变文档的内容,可将 defer 属性加入到 <script> 标签中,以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。 但是如果js脚本会改变文档内容,那这个方法是不适合的。