前言在面试题中,经常会出现与“发布-订阅”模式相关的问题,比如考察我们对Vue响应式的理解,也会有直接的问题测试我们对“发布-订阅”模式或者观察者模式的理解,甚至是一些手写的算法题。今年3月参加一家安全公司的面试时,被要求写代码实现“发布-订阅”模型。当时,我很后悔没有准备好,所以没有回答。可见“发布订阅”模式是一个非常重要的设计模式,接下来我们一起学习。观察者模式vs“发布-订阅”模式首先要澄清的是,虽然两者很相似,但它们是不同的。观察者模式只涉及两个关键角色,即发布者和订阅者。观察者模式定义了一对多的依赖关系,允许多个观察者对象同时监视一个目标对象。当目标对象的状态发生变化时,会通知所有观察者对象,使其自动更新。发布者的行为:添加订阅者,移除订阅者,通知所有订阅者代码实现如下://定义发布者类classPublisher{constructor(){//创建订阅者this.observers=[]}//添加订阅者add(observer){this.observers.push(observer)}//移除订阅者remove(observer){this.observers.map((item,i)=>{if(item===observer){this.observers.splice(i,1)}})}//通知所有订阅者notify(){this.observers.map((observer)=>{observer.update(this)})}}订阅者行为:被通知执行代码实现://定义订阅者类classObserver{constructor(){console.log('createsubscriber')}update(){console.log('subscriberupdate')}}“发布订阅”模式类似于观察者模式,但在这种模式下,发布者根本不需要感知订阅者,也不关心它是如何实现回调方法的。事件的注册和触发发生在独立于双方的第三方平台(事件总线)上。在发布-订阅模式下,实现了完全的解耦。Vue2.x响应式原理探索响应式实现过程官方讲解将对象数据存储在Vue数据中Vue遍历这个对象的每个属性,通过Object.defineProperty()方法进行数据劫持,为每个属性添加getter/setter每个组件实例对应一个Watcher实例,它将收集所有已被触及的属性依赖项。如果数据发生变化,它会收到通知,watcher会使其关联的组件实例发生变化。深入理解响应式原理官网文档v2发布订阅模式vue响应式原理中具体应用初始化过程classVue{constructor(options){this.$options=options;this.$data=options.data;//响应式处理数据选项observe(this.$data);//代理数据到vmproxy(this);//执行编译newCompile(options.el,this);}}发布者/伪订阅者:Observeobserve在上一篇文章中代表的是一个订阅者,而这里他更多的是一个发布者,负责执行发布者权利,添加订阅者,并通知订阅者发生变化。functionobserve(obj){if(typeofobj!=="object"||obj==null){返回;}新的观察者(obj);}classObserver{constructor(value){this.value=value;目的。keys(value).forEach((key)=>{defineReactive(value,key,value[key]);});}}defineReactive是一个很重要的方法,为每个key创建一个Dep实例//为每个键创建一个Dep管家constdep=newDep();//使用defineProperty为每个键创建一个getter/setterObject.defineProperty(obj,key,{get(){Dep.target&&dep.addDep(Dep.target);//Dep.target是Watcher实例returnval;},//监听变化set(newVal){if(newVal===val)return;//通知dep执行update方法dep.notify();},});}事件中心:Depmanager,管理真实的订阅者WactherDep收集组件实例Wacther//PublisherclassDep{constructor(){this.deps=[];中对应同一个key的所有订阅者;//依赖管理}addDep(dep){this.deps.push(部门);}notify(){//其实就是被调用的watcher中的update事件this.deps.forEach((dep)=>dep.update());}}实际订阅者:Watcher触发get,将watcher添加到key对应的Dep中//订阅者负责更新视图classWatcher{constructor(vm,key,updater){this.vm=vmthis.key=keythis.updaterFn=updater//创建实例时,将当前实例赋值给Dep.target的静态属性Dep.target=this//读取key,触发get并将watcher添加到对应的Depkeyvm[key]//EmptyDep.target=null}//以后会执行dom更新函数,update()被dep调用{this.updaterFn.call(this.vm,this.vm[this.key])}}VUE2.X响应式的局限性由于js的局限性,Vue无法检测数组和对象的变化,但是有一些方法可以绕过这些限制并保持响应式。objectVue无法检测到属性的添加或移除,因为Vue在初始化实例时会对属性进行getter/setter转换,所以只有实例初始化时存在于data中的属性才是响应式的。对于已经创建的实例,Vue不允许动态添加根级响应式属性。但是,可以使用Vue.set(object,propertyName,value)方法或其别名vm.$set向嵌套对象添加响应式属性。例如:Vue.set(vm.someObject,'b',2)this.$set(this.someObject,'b',2)为现有对象分配多个新属性,使用原始对象和要混合的对象在Object属性中一起创建一个新对象。//而不是`Object.assign(this.someObject,{a:1,b:2})`this.someObject=Object.assign({},this.someObject,{a:1,b:2})arrayVue无法检测到以下数组变化:当你直接通过索引设置一个数组项时,eg:vm.items[indexOfItem]=newValue当你修改数组的长度时,eg:vm.items.length=newLength来解决第一类问题,下面两个方法可以达到和vm.items[indexOfItem]=newValue一样的效果,同样会在响应式系统中触发状态更新://Vue.setVue.set(vm.items,indexOfItem,newValue)//Array.prototype.splicevm.items.splice(indexOfItem,1,newValue)你也可以使用vm.$set实例方法,它是全局方法Vue.set的别名:vm.$set(vm.items,indexOfItem,newValue)为了解决第二类问题,可以使用splice:vm.items.splice(newLength)感谢这篇文章讨论了发布-订阅模式和观察者模式,也讨论了publish-subscribemodeinvue2.xresponse如果公式中的application有什么不对的,欢迎指正。下一篇我会继续探讨vue3.x的响应式原理。如果觉得写的还可以,请给我一个赞,这样我会更有动力继续下去。向下知识输出!谢谢朋友们的观看!
