【源碼解析】Vue.js的監聽實現

本文地址: http://www.iamaddy.net/2016/1...javascript

一說到監聽,固然就離不了設計模式中鼎鼎大名的觀察者模式。舉個例子,你家後院着火了,可必定要等到煙霧很大火光很亮你才能發現啊,但是當你安裝了一個火災預警器,當發生火災就立馬可以通知到你了。這就是一個典型的觀察者模式。固然也還有一些其餘變種,好比發佈/訂閱(publish/subscribe)模式。vue

咱們知道若是要將數據和視圖關聯起來,在數據變動的時候,同步視圖,同理視圖變動,數據也發生變化。vue.js是怎麼實現這個的呢?下面咱們來揭開它的神祕面紗。
demo:java

<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發送廣播。react

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的更新函數,具體可參考源碼。ajax

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函數負責來更新express

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就關聯在一塊兒了。
圖片描述設計模式

觀察者模式

林林總總的mvc或者mvvm框架基本也都是利用了觀察者模式,這個也很是有用,尤爲在複雜的系統之中。數組

利用觀察者模式,在典型的ajax應用中,回調的處理邏輯能夠不跟請求耦合在一塊,這樣邏輯上也會更加清晰。以下是一個簡單的發佈/訂閱模式的實現mvc

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對應起來就好。怎麼使用呢?app

<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>

最後一個將不會打印出來,由於已經取消訂閱了。

相關文章
相關標籤/搜索