Skip to content

发布订阅模式 #47

Description

@Wscats

emit和on

这一点有点像vuexredux里面的某部分概念,也跟$emit$on和node自带的event模块作用很相像,其实可以这样理解,如果单独用对象把数据存起来,数据改变的时候没有人会追踪到,所以这里在每次改变数据前都放入一个或多个回调函数形成队列去监听on方法,这些回调函数在队列中等待,直到触发了某些机制,这些函数才按顺序逐一回来触发emit方法,从而可以在这个时刻监听到新的数据变化并完成逻辑

letweux={};// 这次换成一个对象类型的缓存列表weux.list={};weux.on=function(key,fn){// 如果对象中没有对应的key值// 也就是说明没有订阅过// 那就给key创建个缓存列表if(!this.list[key]){this.list[key]=[];}// 把函数添加到对应key的缓存列表里this.list[key].push(fn);};weux.emit=function(key,param){// 或者let key = [].shift.call(arguments);// 或者let fns = this.list[key];// 根据获取改函数数组队列letfns=this.list[key];// 如果缓存列表里没有函数就返回falseif(!fns||fns.length===0){returnfalse;}// 遍历key值对应的缓存列表// 依次执行函数的方法fns.forEach(fn=>{// 传入参数fn(param);// 或者 fn.apply(this, arguments);});};// 测试用例weux.on('test',(param)=>{console.log('位置:'+param.position);console.log('技能:'+param.skill);});weux.emit('test',{position: '前端',skill: ['ps','css','js']});

简单的队列

可以没有仓库存放中介值,也可以$emit$on的健值对,只有一个简单的队列,按放入的顺序,然后按顺序重新执行出来

classSubject{constructor(){this.observers=[]}addObserver(observer){this.observers.push(observer)}removeObserver(observer){varindex=this.observers.indexOf(observer)if(index>-1){this.observers.splice(index,1)}}notify(){this.observers.forEach(observer=>{observer.update()})}}letsubject=newSubject()classObserver{constructor(vm,key,callback){this.vm=vm// mvvm 对象this.key=key// data 的属性this.callback=callback//callback}// 更新值update(){console.log("update")this.callback()}}subject.addObserver(newObserver(document.body,{name: "wscats"},()=>{console.log("回调函数")}))console.log(subject)subject.notify();

在上面的基础上添加健值对,可以改写成下面这样,这样写可以应付大部分发布订阅模式的情况

classSubject{constructor(){this.observers=[]}addObserver(key,observer){varindex=this.observers.indexOf(observer)if(!this.observers[key]){// 没有创建新的队列this.observers[key]=[]}this.observers[key].push(observer)}removeObserver(observer){varindex=this.observers.indexOf(observer)if(index>-1){this.observers.splice(index,1)}}// 这里其实不但可以放key,还可以再增加一个参数传进观察者里面,在触发的时候,使用该函数notify(key){this.observers[key].forEach(observer=>{observer.update()})}}letsubject=newSubject()// 可以添加观察者,并在里面存着数据和方法,并保留着一个update方法给notify时候触发classObserver{constructor(vm,key,callback){this.vm=vm// mvvm 对象this.key=key// data 的属性this.callback=callback//callback}// 更新值update(){console.log("update")this.callback()}}subject.addObserver("test1",newObserver(document.body,{name: "wscats"},()=>{console.log("回调函数1")}))subject.addObserver("test2",newObserver(document.body,{name: "wscats"},()=>{console.log("回调函数2")}))subject.notify("test1");subject.notify("test2");console.log(subject)

类似vuex和redux

继续往下改造,可以改成类似vuex的模式,在仓库中留着一份共享的数据,然后利用on缓存回调函数事件放入队列里面,然后利用emit在合适的时候释放该队列

letwuex={store: {position: '温哥华',skill: ['jsx','ts']},list: {},on: function(key,fn){// 如果有则继续加队列if(!this.list[key]){// 没有创建新的队列this.list[key]=[];}this.list[key].push(fn);},emit: function(key,param){letfns=this.list[key];// 遍历更改仓库的值for(pinparam){this.store[p]=param[p]}// 触发数组中的回调函数fns.forEach(fn=>{fn(param);});}};wuex.on('test',(param)=>{console.log('位置:'+param.position);console.log('技能:'+param.skill);});wuex.on('test',(param)=>{console.log('位置:'+param.position);console.log('技能:'+param.skill);});wuex.emit('test',{position: "香港",skill: ['ps','css','js']});

组件通信

发布订阅模式可以用在,两个组件之间的通信

实现双向数据绑定

配合Object.defineProperty实现类似Vue的双向数据绑定

letstore={list: {},on: function(key,fn){// 如果有则继续加队列if(!this.list[key]){// 没有创建新的队列this.list[key]=[];}this.list[key].push(fn);},emit: function(key,param){letfns=this.list[key];fns.forEach(fn=>{fn(param);});}};classVue{constructor(options={}){this.$el=document.querySelector(options.el);letdata=this.data=options.data;Object.keys(data).forEach((key)=>{this.proxyData(key);});this.watcherTask={};// 需要监听的任务列表this.observer(data);// 初始化劫持监听所有数据this.compile(this.$el);// 解析dom}// 把data挂到Vue实例化后的vm对象里面,vm.data.name获取或者改变,则触发get或者setproxyData(key){letthat=this;Object.defineProperty(that,key,{configurable: false,enumerable: true,get(){returnthat.data[key];},set(newVal){console.log(newVal)that.data[key]=newVal;}});}observer(data){letthat=thisObject.keys(data).forEach(key=>{letvalue=data[key]this.watcherTask[key]=[]Object.defineProperty(data,key,{configurable: false,enumerable: true,get(){returnvalue},set(newValue){console.log(key)if(newValue!==value){value=newValue// 发布// 更改vm的数据的时候触发set,然后会触发compileText的订阅store.on监听事件store.emit(key,{})}}})})}compile(el){varnodes=el.childNodes;for(leti=0;i<nodes.length;i++){constnode=nodes[i];if(node.nodeType===3){vartext=node.textContent.trim();//移除字串前後的空白字元以及行結束字元。if(!text)continue;this.compileText(node,'textContent')}}}compileText(node,type){varself=this;// 匹配出该节点下的{{}}这种写法letreg=/\{\{(.*?)\}\}/g,txt=node.textContent;if(reg.test(txt)){node.textContent=txt.replace(reg,(matched,value)=>{// matched {{name}}// value name// 订阅store.on(value,()=>{//node, this, value, typenode[type]=self.data[value]});returnthis[value]})}}}varvm=newVue({el: "#demo",data: {skill: "ps"}})

ES6的写法

classSubject{constructor(){this.observers=[]}addObserver(key,observer){varindex=this.observers.indexOf(observer)if(!this.observers[key]){// 没有创建新的队列this.observers[key]=[]}this.observers[key].push(observer)}removeObserver(observer){varindex=this.observers.indexOf(observer)if(index>-1){this.observers.splice(index,1)}}// 这里其实不但可以放key,还可以再增加一个参数传进观察者里面,在触发的时候,使用该函数notify(key){this.observers[key].forEach(observer=>{observer.update()})}}// 可以添加观察者,并在里面存着数据和方法,并保留着一个update方法给notify时候触发classObserver{constructor(node,vm,value,type,callback){this.node=node// node节点this.vm=vm// mvvm 对象this.value=value// data 的属性this.type=type// node类型this.callback=callback//callback}// 更新值update(){console.log("update")this.callback()}}classVue{constructor(options={}){this.$el=document.querySelector(options.el);letdata=this.data=options.data;Object.keys(data).forEach((key)=>{this.proxyData(key);});this.subject=newSubject();// 需要监听的任务列表this.observer(data);// 初始化劫持监听所有数据this.compile(this.$el);// 解析dom}// 把data挂到Vue实例化后的vm对象里面,vm.data.name获取或者改变,则触发get或者setproxyData(key){letthat=this;Object.defineProperty(that,key,{configurable: false,enumerable: true,get(){returnthat.data[key];},set(newVal){console.log(newVal)that.data[key]=newVal;}});}observer(data){letthat=thisObject.keys(data).forEach(key=>{letvalue=data[key]this.subject.observers[key]=[]Object.defineProperty(data,key,{configurable: false,enumerable: true,get(){returnvalue},set(newValue){console.log(key)if(newValue!==value){value=newValue// 发布// 更改vm的数据的时候触发set,然后会触发compileText的订阅store.on监听事件that.subject.notify(key)}}})})}compile(el){varnodes=el.childNodes;for(leti=0;i<nodes.length;i++){constnode=nodes[i];if(node.nodeType===3){vartext=node.textContent.trim();//移除字串前後的空白字元以及行結束字元。if(!text)continue;this.compileText(node,'textContent')}}}compileText(node,type){varself=this;// 匹配出该节点下的{{}}这种写法letreg=/\{\{(.*?)\}\}/g,txt=node.textContent;if(reg.test(txt)){node.textContent=txt.replace(reg,(matched,value)=>{// matched {{name}}// value name// 订阅this.subject.addObserver(value,newObserver(node,this,value,type,()=>{node[type]=self.data[value]}))returnthis[value]})}}}varvm=newVue({el: "#demo",data: {skill: "ps"},})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions