前面记录了跨浏览器的事件对象,那么dom的addEventListener
,removeEventListener
到底是怎么实现的呢?还有vue中的$on
,$emit
,$once
,$off
又是如何实现的呢?以及vue中的依赖追踪是怎么实现的呢?
两者的区别
可以参考知乎的这一篇文章,https://zhuanlan.zhihu.com/p/51357583
或者自行百度。其实我觉得他们的实现思想很像,但是还是有点差别吧,可以自行体会。
发布订阅模式
dom事件机制其实就是最典型的发布订阅模式的实现,至于啥是发布订阅模式,可以自行百度,这里就不过多去介绍他的概念。可以看看下面的代码,应该就差不多能明白个大概了...
今天我们来实现一个简单的发布订阅模式EventEmitter,实现的是类似vue中的$on
,$emit
,$once
,$off
方法。废话就不多说了,可以直接看代码,毕竟都是程序员,任何能用代码表示的用过多的语言都是徒然...(其实是程序员小姐姐懒),具体可以参考vue源码中的实现
class EventEmitter{
constructor(){
//初始值为空对象
this._events=Object.create(null)
}
//监听事件
on(event,fn){
//如果是数组,则循环遍历执行on
if(Array.isArray(event)){
event.forEach(item=>{
this.on(item,fn)
})
}else{
(this._events[event]||(this._events[event]=[])).push(fn)
}
}
//关闭事件
off(event,fn){
//如果参数为空,则关闭所有的事件
if(!arguments.length){
//设置为初始值
this._events=Object.create(null)
return
}
//event支持数组,即设置多个事件
if(Array.isArray(event)){
event.forEach(item=>{
this.off(item,fn)
})
return
}
//如果传进来的事件没有emit,则返回
if(!this._events[event]) return
//如果没有传回调函数,则默认移除该事件下的所有回调
if(!fn){
this._events[event]=[]
return
}
//否则,移除与fn相同的回调
if(fn){
const cbs=this._events[event]
let i=this._events[event].length
while(i--){
if(cbs[i]===fn||cbs[i].fn===fn){
//这里可以直接用cbs,是因为this._events[event]是引用类型,cbs指向的是同一个数据
cbs.splice(i,1)
continue
}
}
}
}
//只执行一次
once(event,fn){
//自定义拦截器
const on=()=>{
this.off(event,on)
fn.apply(this,arguments)
}
on.fn=fn
console.log(on)
this.on(event,on)
}
//派发事件
emit(event,...args){
const cbs=this._events[event]
if(cbs){
cbs.forEach(fn=>{
fn.apply(this,args)
})
}
}
}
const baseEvent=new EventEmitter()
function cb(value){
console.log("hello "+value)
}
baseEvent.once("click",cb)
// baseEvent.off("click")
baseEvent.emit("click",'2020')
console.log(baseEvent._events)
观察者模式
vue的双向绑定,依赖追踪其实用的就是观察者模式。
把收集来的依赖都存在Dep实例中的subs中,而每一个依赖都是一个watcher,watcher中有通知组件实例更新的方法。下面实现一个简版的,具体可以去看vue的源码。
class Subscribe{
constructor(){
this.subs=[]
}
addSub(sub){
this.subs.push(sub)
}
removeSub(sub){
let index=this.subs.indexOf(sub)
if(index>-1){
this.subs.splice(index,1)
}
}
notify(){
const subs=this.subs.slice()
subs.forEach(sub=>{
sub.update()
})
}
}
class Observer{
constructor(name){
this.name=name
}
update(){
console.log('name is changed',this.name)
}
}
let subscribe=new Subscribe()
let ob1=new Observer("路飞")
let ob2=new Observer("罗杰")
subscribe.addSub(ob1)
subscribe.addSub(ob2)
subscribe.removeSub(ob2)
subscribe.notify()