一、异步编程的方法:回调函数、事件监听、发布/订阅、Promises对象
方法1:回调函数(callbacks)
优点:简单、容易理解和部署
缺点:
1)不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱
2)每个任务只能指定一个回调函数
基础用法:
// 主函数 function mainFunction(callback) { // do something // ... // call the callback callback('stuff', 'goes', 'here') } // 回调函数 function foo(a, b, c) { alert(a + " " + b + " " + c); } // 调用主函数,传入回调函数 mainFunction(foo)
高级用法:
// call function Thing(name) { this.name = name; } Thing.prototype.doSomething = function(callback, salutation) { // Call our callback, but using our own instance as the context callback.call(this, salutation); } function foo(salutation) { alert(salutation + " " + this.name); } var t = new Thing('Joe'); t.doSomething(foo, 'Hi'); // Alerts "Hi Joe" via `foo` // apply function Thing(name) { this.name = name; } Thing.prototype.doSomething = function(callback) { // Call our callback, but using our own instance as the context callback.apply(this, ['Hi', 3, 2, 1]); } function foo(salutation, three, two, one) { alert(salutation + " " + this.name + " – " + three + " " + two + " " + one); } var t = new Thing('Joe'); t.doSomething(foo); // Alerts "Hi Joe – 3 2 1" via `foo`
方法2:事件监听
优点:
1)可以绑定多个事件,每个事件可以指定多个回调函数
2)可以“去耦合”(Decoupling),有利于实现模块化
缺点:整个程序都要变成事件驱动型,运行流程会变得很不清晰
实例:
1)原生态的事件绑定函数 addEventListener
const eventOne = function(){ alert("第一个监听事件"); } function eventTwo(){ alert("第二个监听事件"); } window.onload = function(){ var btn = document.getElementById("yuanEvent"); //addEventListener:绑定函数 btn.addEventListener("click",eventOne); btn.addEventListener("click",eventTwo); }
最后输出:第一个监听事件 和 第二个监听事件
2)当同一个对象使用.onclick的写法触发多个方法的时候,后一个方法会把前一个方法覆盖掉,也就是说,在对象的onclick事件发生时,只会执行最后绑定的方法。而用事件监听则不会有覆盖的现象,每个绑定的事件都会被执行。如下:
window.onload = function(){ var btn = document.getElementById("yuanEvent"); btn.onclick =function(){ alert("第一个事件"); } btn.onclick =function(){ alert("第二个事件"); } btn.onclick =function(){ alert("第三个事件"); } }
最后只输出:第三个事件,因为后一个方法都把前一个方法覆盖掉了。
方法3:发布/订阅
性质与“事件监听”类似,但是明显优于后者。
1)可以广泛应用于异步编程,它可以代替我们传统的回调函数,我们不需要关注对象在异步执行阶段的内部状态,我们只关心事件完成的时间点。
2)取代对象之间硬编码通知机制,一个对象不必显式调用另一个对象的接口,而是松耦合的联系在一起,虽然不知道彼此的细节,但不影响相互通信。更重要的是,其中一个对象改变不会影响另一个对象。
// 定义发布/订阅模型 const event = { // 设置缓存列表,存放订阅者的回调函数列表 list : [], // 设置订阅者 listen : function(key , fn){ if(!this.list[key]){ this.list[key] = []; } // 将订阅的消息添加到缓存列表中 this.list[key].push(fn); }, // 发布事件 trigger : function() { const key = Array.prototype.shift.call(arguments), fns = this.list[key]; if(!fns || fns.length === 0){ return false; } for(let i = 0 , fn ; fn = fns[i++];){ fn.apply(this, arguments); } }, // 取消订阅 remove : function(key , fn){ const fns = this.list[key]; // 如果key对应的消息没有订阅过的话,则返回 if(!fns) { return false; } // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅 if(!fn) { delete this.list[key]; //如果没有后续参数,则删除整个回调数组 }else { for(let i = fns.length - 1 ; i>=0 ;i--) { const _fn = fns[i]; if(_fn === fn) { fns.splice( i, 1); // 删除订阅者的回调函数 } } } } }; const initEvent = function(obj) { for(let i in event) { obj[i] = event[i]; } }; const shoeobj = {}; initEvent(shoeobj); shoeobj.listen('abcd' , function(a, b) { console.log(a); console.log(b); }) shoeobj.listen('efgh' , function(a, b){ console.log(a); console.log(b); }) shoeobj.trigger('abcd' , 100 ,200); // 100 200 shoeobj.trigger('efgh' , 300, 500); // 300 500 shoeobj.remove('abcd'); shoeobj.trigger('abcd', 20, 50); // false
上述的参考链接:Javascript实现简单地发布订阅模式 - 曼施坦因 - 博客园
另一种表达形式:
function Dep() { this.subs = [] } Dep.prototype.addsub = function (sub) { this.subs.push(sub) } Dep.prototype.notify = function () { this.subs.forEach(sub => sub.update()) } function Watcher(fn) { this.fn = fn } Watcher.prototype.update = function () { this.fn() } let watcher = new Watcher(function () { console.log(1) }) let dep = new Dep() dep.addsub(watcher) console.log(dep.subs) dep.notify()
方法4:Promises对象
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。