关于javascript:面试官请实现Javascript发布订阅模式

41次阅读

共计 4350 个字符,预计需要花费 11 分钟才能阅读完成。

简介

公布 - 订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态产生扭转的时候,所有依赖他的对象都会失去告诉。

回顾已经

作为一名前端开发人员,给 DOM 节点绑定事件可是再频繁不过的事件。比方如下代码

    document.body.addEventListener('click',function () {alert(2333);
    },false);
    document.body.click();// 模仿点击事件 

这里咱们订阅了 document.body 的 click 事件,当 body 被点击的时候,他就向订阅者公布这个音讯,弹出 2333. 咱们也能够随便的减少和删除订阅者,当音讯一公布,所有的订阅者都会收到音讯。

    document.body.addEventListener('click',function () {alert(11111);
    },false);
    document.body.addEventListener('click',function () {alert(222);
    },false);
    document.body.addEventListener('click',function () {alert(333);
    },false);
    document.body.click();// 模仿点击事件 

值得注意的是,手动触发事件这里咱们间接用了 document.body.click();然而更好的做法是 IE 下用 fireEvent,规范浏览器下用 dispatchEvent,如下:

    let fireEvent = function (element,event) {if (document.createEventObject) {var evt = document.createEventObject();
            return element.fireEvent('on'+event,evt);
        }else{var evt = document.createEvent('HTMLEvents');
            evt.initEvent(event,true,true);
            return element.dispatchEvent(evt);
        }
    }
    document.addEventListener('shout',function (event) {alert('shout');
    })
    fireEvent(document,'shout');

畅谈当初

人的日常生活离不开各种人际交涉,比方你的敌人有很多,这时候你要结婚了,要以你为发布者,关上你的通讯录,挨个打电话告诉各个订阅者你要结婚的音讯。形象一下,实现公布 - 订阅模式须要:

  1. 发布者(你)
  2. 缓存列表(通讯录,你的敌人们相当于订阅了你的所有音讯)
  3. 公布音讯的时候遍历缓存列表,顺次触发外面寄存的订阅者的回调函数(挨个打电话)
  4. 另外,回调函数中还能够增加很多参数,,订阅者能够接管这些参数,比方你会通知他们婚礼工夫,地点等,订阅者收到音讯后能够进行各自的解决。
let yourMsg = {};
yourMsg.peopleList = [];
yourMsg.listen = function (fn) {this.peopleList.push(fn);
}
yourMsg.triger = function () {for(var i = 0,fn;fn=this.peopleList[i++];){fn.apply(this,arguments);
    }
}

yourMsg.listen(function (name) {console.log(`${name} 收到了你的音讯 `);
})
yourMsg.listen(function (name) {console.log('哈哈');
})

yourMsg.triger('张三');
yourMsg.triger('李四');

  • 以上就是一个简略的公布 - 订阅的实现,然而咱们会发现订阅者会收到发布者公布的每一条信息,如果李四比拟明朗,不想听到你结婚的音讯,只想听到你的坏消息,比方你被开革了,他就心里快乐。这时候咱们就须要加一个 key,让订阅者只订阅本人感兴趣的音讯。
let yourMsg = {};
yourMsg.peopleList ={};
yourMsg.listen = function (key,fn) {if (!this.peopleList[key]) { // 如果没有订阅过此类音讯,创立一个缓存列表
        this.peopleList[key] = [];}
    this.peopleList[key].push(fn);
}
yourMsg.triger = function () {let key = Array.prototype.shift.call(arguments);
    let fns = this.peopleList[key];
    if (!fns || fns.length == 0) {// 没有订阅 则返回
        return false;
    }
    for(var i=0,fn;fn=fns[i++];){fn.apply(this,arguments);
    }
}

yourMsg.listen('marrgie',function (name) {console.log(`${name} 想晓得你结婚 `);
})
yourMsg.listen('unemployment',function (name) {console.log(`${name} 想晓得你就业 `);
})

yourMsg.triger('marrgie','张三');
yourMsg.triger('unemployment','李四');

  • 你须要公布音讯,同样的所有的人都有朋友圈,也都须要公布音讯,因而咱们有必要把公布 - 订阅的性能提取进去,放在一个独自的对象内,谁须要谁去动静装置公布 - 订阅性能 (installEvent 函数实现了动静装置公布 - 订阅性能)。参考 前端手写面试题具体解答
var event = {peopleList:[],
    listen:function (key,fn) {if (!this.peopleList[key]) { // 如果没有订阅过此类音讯,创立一个缓存列表
        this.peopleList[key] = [];}
        this.peopleList[key].push(fn)
    },
    trigger:function () {let key = Array.prototype.shift.call(arguments);
        let fns = this.peopleList[key];
        if (!fns || fns.length == 0) {// 没有订阅 则返回
            return false;
        }
        for(var i=0,fn;fn=fns[i++];){fn.apply(this,arguments);
        }
    }
}

var installEvent  = function (obj) {for(var i in event){obj[i] = event[i];
    }
}

let yourMsg = {};
installEvent(yourMsg);
yourMsg.listen('marrgie',function (name) {console.log(`${name} 想晓得你结婚 `);
})
yourMsg.listen('unemployment',function (name) {console.log(`${name} 想晓得你就业 `);
})

yourMsg.trigger('marrgie','张三');
yourMsg.trigger('unemployment','李四');
  • 有工夫咱们须要勾销订阅的事件,比方李四是你的好敌人,然而因为一件事件,你俩闹掰了,你把他从你的通讯录中给删除掉了,这里咱们给 event 减少一个 remove 办法;
remove:function (key,fn) {var fns = this.clientList[key];
      if(!fns){return false;}  
      if(!fn){fns && (fns.length=0)
      }else{for (let index = 0; index < fns.length; index++) {const _fn = fns[index];
              if(_fn === fn){fns.splice(index,1);
              }
          }
      }
    }

公布 - 订阅的程序探讨

咱们通常所看到的都是先订阅再公布,然而必须要恪守这种程序吗?答案是不肯定的。如果发布者先公布一条音讯,然而此时还没有订阅者订阅此音讯,咱们能够不让此音讯隐没于宇宙之中。就如同 QQ 离线音讯一样,离线的音讯被保留在服务器中,接管人下次登录之后,才会收到此音讯。同样的,咱们能够建设一个寄存离线事件的堆栈,当事件公布的时候,如果此时还没有订阅者订阅这个事件,咱们临时把公布事件的动作包裹在一个函数里,这些包装函数会被存入堆栈中,等到有对象来订阅事件的时候,咱们将遍历堆栈并顺次执行这些包装函数,即重发外面的事件,不过离线事件的生命周期只有一次,就像 qq 未读音讯只会提醒你一次一样。

JavaScript 实现公布 - 订阅模式的便利性

因为 JavaScript 有回调函数这个劣势存在,咱们写开发 - 订阅显得更简略一点。传统的公布 - 订阅比方 Java 通常会把订阅者本身当成援用传入发布者对象中,同时订阅者对象还需提供一个名为诸如 update 的办法,供发布者对象在适合的时候调用。上面代码用 js 模仿下传统的实现。

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();
}

var dep = new Dep();
dep.addSub(new Watcher(function () {console.log('okokok');
}))
dep.notify();

小结

  • 公布 - 订阅的劣势很显著,做到了工夫上的解耦和对象之间的解耦,从架构上看,MVC,MVVM 都少不了公布 - 订阅的参加,咱们罕用的 Vue 也是基于公布 - 订阅的,最近会抽时间写下 vue 的源码实现,同样的 node 中的 EventEmitter 也是公布订阅的,之前也手写过它的实现。
  • 公布 - 订阅同时也是有毛病存在的,创立订阅者自身要耗费肯定的工夫和内存,而且当你订阅一个音讯当前,可能此音讯最初都未产生,然而这个订阅者会始终存在于内存中。如果程序中大量应用公布 - 订阅的话,也会使得程序跟踪 bug 变得艰难。

正文完
 0