【Vue原理】Watch - 源碼版

寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】數組

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧函數

【Vue原理】Watch - 源碼版 學習

今天繼續探索 Watch 源碼,廢話很少說了this

帶着個人幾個疑問開始prototype

一、何時初始化

二、怎麼肯定監聽哪些值

三、深度監聽怎麼回事

四、怎麼觸發個人函數

這些問題的答案會摻雜在源碼的解析中,我發現這幾篇的寫做套路都差很少.....3d

也能夠看查一下個人白話版code

【Vue原理】Watch - 白話版


何時初始化

首先,從這個問題開始咱們今天的探索之旅,請看源碼對象

function Vue(){
    ... 其餘處理
    initState(this)

    ...解析模板,生成DOM 插入頁面

}



function initState(vm) {


    ...處理 data,props,computed 等數據

    if (opts.watch) {
        initWatch(this, vm.$options.watch);
    }
}

沒錯,當你調用 Vue 建立實例過程當中,會去處理各類選項,其中包括處理 watchblog


initWatch

處理 watch的方法是 initWatch,下面就呈上 源碼遞歸

function initWatch(vm, watch) {    

    for (var key in watch) {    

        var watchOpt = watch[key];
        createWatcher(vm, key, handler);
    }
}

而後這段源碼並無作什麼驚天地泣鬼神的事情,只是簡簡單單的一個遍歷,而後每一個watch 都使用一個叫什麼 createWatcher 的東西去處理

這個函數究竟是幹嗎的?暗藏着什麼祕密,歡迎來到咱們今晚的 《走近科學》《源碼解析》

createWatcher 來看看他的真身

function createWatcher(
    // expOrFn 是 key,handler 多是對象
    vm, expOrFn, handler,opts

) {    

    // 監聽屬性的值是一個對象,包含handler,deep,immediate

    if (typeof handler ==="object") {
        opts= handler
        handler = handler.handler
    }    



    // 回調函數是一個字符串,從 vm 獲取

    if (typeof handler === 'string') {
        handler = vm[handler]
    }    



    // expOrFn 是 key,options 是watch 的所有選項

    vm.$watch(expOrFn, handler, opts)
}

大概就這樣吧

一、獲取到監聽回調

二、調用 vm.$watch

一、獲取監聽回調

首先,你傳入的 watch 配置多是這三種(還有更多,差很少,不解釋,累死我)

公衆號

若是配置是個對象,就取handler 字段

若是配置是函數,那麼直接就是 監聽回調

若是配置是字符串,從實例上獲取函數

二、調用 vm.$watch

看着這個方法,簡直 mmp,還有完沒完,我從一個函數進入一個函數,又從這個函數進入到另外一個函數,迷宮啊這是.....

顯然你不用急,下面還有更多......

好吧,仍是先看源碼

Vue.prototype.$watch = function(
    // expOrFn 是 監聽的 key,cb 是監聽回調,opts 是全部選項

    expOrFn, cb, opts

){    

    // expOrFn 是 監聽的 key,cb 是監聽的回調,opts 是 監聽的全部選項

    var watcher = new Watcher(this, expOrFn, cb, opts);    



    // 設定了當即執行,因此立刻執行回調

    if (opts.immediate) {
        cb.call(this, watcher.value);
    }
};

看完了吧?這麼短,大家確定看得懂的啦,就兩件事

一、判斷是否當即執行監聽回調

若是你設置了 immediate 的話,表示不用等我數據變化,初始化時立刻執行一遍,執行的代碼就是直接調用 回調,綁定上下文,傳入監聽值

二、每一個 watch 配發 watcher

代碼從這裏開始變得沉重,各位觀衆,喝口水,恰口飯,屏息觀看操做

看看 watcher 的源碼

「watcher 的源碼以前的文章也講過不少,可是對於每種選項的涉及的細節是不同的,因此每次都放上來,可是隻放跟本內容相關的部分代碼,其餘的省去以便咱們快速理解」

var Watcher = function (vm, key, cb, opt) {  

    this.vm = vm;    

    this.deep = opt.deep;    

    this.cb = cb;  

    // 這裏省略處理 xx.xx.xx 這種較複雜的key
    this.getter = function(obj) {        

        return obj[key]

    };    

    // this.get 做用就是執行 this.getter函數

    this.value = this.get();
};

再看看,新建 watcher 的時候 ,傳入了什麼

一、監聽的 key

二、監聽回調 (Watch 中的cb)

三、監聽配置的options

這裏會涉及到三個問題,如今來解釋

一、怎麼對設置的 key 進行監聽?

咱們要先對 Watch 中的 this.getter 的函數進行理解,他的本質是爲了獲取對象的key值

而後 getter 是在 watcher.get 中執行的,看下 get 源碼

// 對本問題進行了獨家簡單化的源碼

Watcher.prototype.get = function() {    

    var value = this.getter(this.vm);    

    return value

};

你能看到,Watch 在結尾會當即執行一次 watcher.get,其中便會執行 getter,便會根據你監聽的key,去實例上讀取並返回,存放在 watcher.value 上

看到了嗎,從實例上讀取屬性,這句話。

首先,watch 初始化以前,data 應該初始化完畢了,每一個 data 數據都已是響應式的

使用例子來講明一下

公衆號

當 watch.getter 執行,而讀取了 vm.name 的時候,name的依賴收集器就會收集到 watch-watcher

因而 name 變化的時候,會能夠通知到 watch,監聽就成功了

二、如何進行深度監聽?

首先,深度監聽,是你設置了 deep 的時候,以下

公衆號

而後,觀察上面的 Watch 源碼,deep 會保存在watcher 中,以便後用

話鋒一轉

上一問題說過,在 新建 watcher 的時候,會立刻執行一個 get,上個問題的 get 源碼簡化不少,把 處理深度監聽的部分去掉了,這裏露出來了

Watcher.prototype.get = function() {
    Dep.target= this

    var value = this.getter(this.vm)    

    if (this.deep)  traverse(value)

    Dep.target= null
    return value
};

沒錯,處理深度監聽只有一條語句!

if (this.deep)  traverse(value)

value 是 getter 從實例上讀取監聽key 獲得的值,沒有疑問

可是 traverse 是何方神聖?come on 讓咱們深刻....

function traverse(val) {    

    var i, keys;    



    // 數組逐個遍歷

    if (Array.isArray(val)) {

        i = val.length;        

        // val[i] 就是讀取值了,而後值的對象就能收集到 watch-watcher

        while (i--) {
           traverse(val[i])
        }
    }    

    else {

        keys = Object.keys(val);
        i = keys.length;        

        // val[keys[i]] 就是讀取值了,而後值的對象就能收集到 watch-watcher

        while (i--) {
           traverse(val[keys[i]])
        }
    }
}

你看它這段代碼長,實際上是個紙老虎,作的就是一個事情,不斷遞歸深刻讀取對象

他的想法是這樣的

由於讀取,就可讓這個屬性收集到 watch-watcher 的原則

就算是深層級的對象,其中的每一個屬性也都是響應式的,每一個屬性都有本身的依賴收集器

經過不斷深刻的讀取每一個屬性,這樣每一個屬性就均可以收集到 watch-watcher 了

這樣無論對象內多深的屬性變化,都會通知到 watch-watcher

因而這樣就完成了深度監聽

三、監聽值變化,如何觸發監聽函數?

經過上面的問題,咱們已經瞭解了大部分了

監聽的數據變化的時候,就能通知 watch-watcher 更新,所謂通知更新,就是手動調用 watch.update

速度看下 watcher.update 源碼

Watcher.prototype.update= function() {    

    var value = this.get();    

    if (this.deep) {        

        var oldValue = this.value;        

        this.value = value;        

        // cb 是監聽回調
        this.cb.call(this.vm, value, oldValue);
    }
};

很簡單嘛,就是讀取一遍值,而後保存新值,接着 調用 監聽回調,並傳入新值和 舊值

ok,就這樣

公衆號

相關文章
相關標籤/搜索