转载

【源码解析】Vue.js的监听实现

一说到监听,当然就离不了设计模式中鼎鼎大名的观察者模式。举个例子,你家后院着火了,可一定要等到烟雾很大火光很亮你才能发现啊,可是当你安装了一个火灾预警器,当发生火灾就立马能够通知到你了。这就是一个典型的观察者模式。当然也还有一些其他变种,比如发布/订阅(publish/subscribe)模式。

我们知道如果要将数据和视图关联起来,在数据变更的时候,同步视图,同理视图变更,数据也发生变化。vue.js是怎么实现这个的呢?下面我们来揭开它的神秘面纱。demo:

<script src="../vue.js"> </script> <div id="app">  <p>       {{ message }}     </p>     <input v-model="message"> </div> <script type="text/javascript"> new Vue({   el: '#app',   data: {     message: 'Hello Vue.js!'   } }); </script> 
set: function reactiveSetter(newVal) {   var value = getter ? getter.call(obj) : val;   if (newVal === value) {     return;   }   if (setter) {     setter.call(obj, newVal);   } else {     val = newVal;   }   childOb = observe(newVal);   dep.notify(); }

这段代码出现在解析data属性的时候,即调用Object.defineProperty方法配置data的属性。一旦属性发生变化,就notify发送广播。

Dep.prototype.notify = function () {   // stablize the subscriber list first   var subs = toArray(this.subs);   for (var i = 0, l = subs.length; i < l; i++) {     subs[i].update();   } };

notify 最终是周知subscribe(订阅者)更新,那么上面的数据变更就是发布者。subscribe是Watcher这个类的实例化对象,在实例化的时候,会传入回调函数来执行update,vue弄了一个队列来执行watcher的更新函数,具体可参考源码。

Watcher.prototype.run = function () {     ……     if (value !== this.value || (isObject(value) || this.deep) && !this.shallow) {       ……       } else {         this.cb.call(this.vm, value, oldValue);       }     }     this.queued = this.shallow = false;   }  };

在Directive(指令)class中实例化了Watcher,_update函数负责来更新

var watcher = this._watcher = new Watcher(this.vm, this.expression, this._update, // callback       {         filters: this.filters,         twoWay: this.twoWay,         deep: this.deep,         preProcess: preProcess,         postProcess: postProcess,         scope: this._scope       });

在解析模板的时候会解析Directive,然后绑定,实例化watcher,这样模板-data就关联在一起了。

【源码解析】Vue.js的监听实现

观察者模式

林林总总的mvc或者mvvm框架基本也都是利用了观察者模式,这个也非常有用,尤其在复杂的系统之中。

利用观察者模式,在典型的ajax应用中,回调的处理逻辑可以不跟请求耦合在一块,这样逻辑上也会更加清晰。如下是一个简单的发布/订阅模式的实现

var PubSub = {}; (function (q) {     var topics = {}, subUid = -1;     q.publish = function (topic) {         if(!topics[topic]){             return false;         }          var subscribers = topics[topic],             len = subscribers ? subscribers.length : 0;          while(len--){             var args = Array.prototype.slice.call(arguments, 1);             args.unshift(topic);             subscribers[len].callback.apply(this, args);         }         return this;     };      q.subscribe = function (topic, callback) {         if(!topics[topic]){             topics[topic] = [];         }          var subuid = (++subUid).toString();          topics[topic].push({             token: subuid,             callback: callback         });          return subuid;     };      q.unsubscribe = function (subid) {         for(var k in topics){             if(topics[k]){                 for(var i = 0, j = topics[k].length; i < j; i++){                     if(topics[k][i].token === subid){                         topics[k].splice(i, 1);                         return subid;                     }                 }             }         }         return this;     }; })(PubSub);

这就是一个简单的订阅发布系统,每注册一个订阅者,其实就是将其回调处理的callback保存在一个字典对象的数组中,字典对象的key值可以随意定义,只要与发布时的key对应起来就好。怎么使用呢?

<script> var messageLogger = function(){         console.log(JSON.stringify(arguments));     };  var subscription = PubSub.subscribe('/newMessage', messageLogger); // {"0":"/newMessage","1":"hello world"} PubSub.publish('/newMessage', 'hello world');  // {"0":"/newMessage","1":["test","a","b","c"]} PubSub.publish('/newMessage', ['test', 'a', 'b', 'c']);  // {"0":"/newMessage","1":{"sender":"hello world","body":"hey man"}} PubSub.publish('/newMessage', {     sender: 'hello world',     body: 'hey man' });  PubSub.unsubscribe(subscription);  PubSub.publish('/newMessage', ['test', 'a', 'b', 'c'], 1); </script> 

最后一个将不会打印出来,因为已经取消订阅了。

原文  https://segmentfault.com/a/1190000005082164
正文到此结束
Loading...