Vue雙向綁定原理(源碼解析)---getter setter


   Vue雙向綁定原理     

 

     大部分都知道Vue是采用的是對象的get 和set方法來實現數據的雙向綁定的過程,本章將討論他是怎么利用他實現的。

     vue雙向綁定其實是采用的觀察者模式,get和set方法只是實現觀察者模式的切入點,即在我們set的時候向觀察者發布消息,執行觀察者的操作,get的時候是為實現set能夠通知watcher進行相關處理做准備。下面我們來看一下數據初始化的流程;

數據初始化流程:

 

       數據在初始化時,會調用方法 defineReactive 為數據綁定dep對象(以備之后使用),在進行掛載時會實例化一個進行頁面更新的watcher($watcher).,該watcher調用渲染函數(上圖的第5步),會調用模板里涉及到的屬性的get方法,即為每個屬性的dep對象加載對應的依賴$watcher,同時在$watcher下備份涉及到的dep對象。數據初始化時主要是調用data下的屬性的get方法,在數據更新時才會調用屬性的set方法,詳細可看下面的代碼注釋。

 

/**
 * 采用觀察者模式進行數據更新的監聽,subject為data下的所有屬性以及子孫屬性等
 * 
 * subject與watcher是多對多的關系
 * 一個subject,如屬性data.A,可能對於處理頁面更新的Watcher,也可能對應$watch函數傳入的表達式更新的Watcher對象,或者觀察計算屬性的Watcher對象等
 *  一個更新頁面的Watcher會對象data下的所有屬性以及子孫屬性等
 * 
 * 
 * */

    function defineReactive(
        obj,
        key,
        val,
        customSetter,
        shallow
    ) {
        var dep = new Dep();

        var property = Object.getOwnPropertyDescriptor(obj, key);
        if (property && property.configurable === false) {
            return
        }

        // cater for pre-defined getter/setters
        var getter = property && property.get;
        if (!getter && arguments.length === 2) {
            val = obj[key];
        }
        var setter = property && property.set;
        // 如果 val為對象,創建一個觀察者Observe實例ob,先創建一個dep實例,賦值給Observe.dep ,保存實例只val.__ob__屬性, 類似val.__ob__ = ob; 
        var childOb = !shallow && observe(val); 
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: function reactiveGetter() {
                var value = getter ? getter.call(obj) : val; //如果屬性之前就定義getter方法,則執行getter方法,並返回屬性值
                if (Dep.target) { // Dep.target為執行更新的watcher對象
                    dep.depend();  //為該屬性添加觀察者(訂閱者),掛在對應的dep對象的subs:[]屬性下,對應的watcher也會存對應的目標關系與watcher.deps與watcher.depIds
                    if (childOb) {
                        //如果是對象,為對象添加該Watcher實例,可用於set, 數組改變,計算屬性等操作
                        childOb.dep.depend();  
                        if (Array.isArray(value)) { //如果value是數據 采用遞歸的方式為為value下的對象成員添加Observe實例屬性 __ob__
                            dependArray(value);
                        }
                    }
                }
                return value
            },
            set: function reactiveSetter(newVal) {
                var value = getter ? getter.call(obj) : val;
                /* eslint-disable no-self-compare */
                if (newVal === value || (newVal !== newVal && value !== value)) {
                    return
                }
                /* eslint-enable no-self-compare */
                if ("development" !== 'production' && customSetter) {//如果是開發環境,者打印一些警告
                    customSetter();
                }
                if (setter) { //如果屬性之前就定義了setter方法,則執行setter方法
                    setter.call(obj, newVal);
                } else {
                    val = newVal;
                }
                childOb = !shallow && observe(newVal);
                dep.notify();  // 數據有改變,通知觀察者進行操作
            }
        });
    }

 

 

 

數據更新流程: 

  這里將講解數據的更新流程,如果我重設了數據如data.A = 5;它將出發A屬性的set方法,該方法會對比當前設置的值與之前的值是否相等,如果不相等,在出發dep.notify()函數,類似觸發更新事件,代用更新操作,該方法會去調用dep對象下所有依賴watcher的update方法,該方法會排除重復的watcher,最終采用微任務或者定時的方式去執行watcher對應的run方法(run方法調用了watcher的get方法),之后將會調用渲染函數,如此又將與初始化的過程過程的第五步一致,調用data下的get方法,同時備份watcher至涉及到的dep對象下,將上次執行備份中涉及到的dep而本次執行沒涉及到dep下的依賴當前watcher刪除,想見 Watcher.prototype.cleanupDeps函數注釋

     在初始化以及數據更新工程,都將調用watcher.get方法,為什么執行該方法就能保證以上功能正常執行呢

     watcher.get方法會去調用watcher實例的getter,如果是進行頁面更新與渲染的watcher,getter方法這是去執行render函數並將render函數生成的vnode進行渲染。再執行render函數時會涉及到調用模板里的data屬性。從而觸發屬性的get方法。那又是采用什么方式保證屬性對應的dep是觸發對應的watcher?見Watcher.prototype.get注釋

 

 Watcher.prototype.get = function get() {
        pushTarget(this);  //該函數的作用是,將Dep.target的值推入堆棧中,並將當前的Watcher實例賦值給Dep.target 
        var value;
        var vm = this.vm;
        try {
            value = this.getter.call(vm, vm);
        } catch (e) {
            if (this.user) {
                handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
            } else {
                throw e
            }
        } finally {
            // "touch" every property so they are all tracked as
            // dependencies for deep watching
       //如果是深度監聽watching,進行遞歸加載監聽觀察者
if (this.deep) { traverse(value); } popTarget(); //將堆棧中棧頂的值彈出堆棧並賦值給 Dep.target this.cleanupDeps();//與上一次的watcher對象依賴下的dep數據對比,清除沒有使用的dep對象 } return value };

   在以上代碼,在初始化的時候執行watcher.get,其中會調用pushTarget(this),將當前的值賦給Dep.target ,即相當於賦給了一個全局變量。之后將執行watcher的this.getter方法,即render函數,在執行這個函數期間Dep.target一直保持為該watcher,以此保證屬性下的dep對象都是對應的watcher,如果在執行render期間有其他watcher執行打斷當前的執行,也會在執行其他watcher之后恢復該值,執行完this.getter執行popTarget();將從堆棧中取出執行上下文的watcher值並賦給Dep.target。

       下面的代碼這是用於保證watcher對象與dep對象的正確依賴關系。同事備份執行watcher依賴的dep對象。

       由於dep對象用於通知watcher執行相關的操作,所以他們之間會有一個多對多的對應關系。即dep需要通知哪些watcher,watcher又注冊到哪些dep對象中,下面的就是保證每次執行之后這個依賴關系都是正確的

 

 /**
     * 備份本次更新的deps對象和depIds,同時將上次Watcher更新依賴的dep實例,但本次Watcher更新更新不依賴的dep實例下依賴Watcher備份數據刪除該Watcher實例
     */
    Watcher.prototype.cleanupDeps = function cleanupDeps() {
        var this$1 = this;

        var i = this.deps.length;
        while (i--) {
            // 獲取上一次觸發Wactcher更新的dep對象--dep
            var dep = this$1.deps[i]; 
            // 如果該對象dep在新的watcher依賴下沒有則清除dep對象下依賴的該Watcher對象
            if (!this$1.newDepIds.has(dep.id)) { 
                dep.removeSub(this$1);
            }
        }
        var tmp = this.depIds;  //將上次更新的depIds緩存
        this.depIds = this.newDepIds;  //備份本次更新的Watcher對應的deps對象的id
        this.newDepIds = tmp; //將上次更新的depId賦值給newDepIds,並在下一行進行清空
        this.newDepIds.clear(); //將newDepIds清空,以備下次更新使用
        tmp = this.deps;    //deps 對象的備份與 depIds邏輯一致
        this.deps = this.newDeps;  //備份本次更新的Watcher對應的deps對象
        this.newDeps = tmp;
        this.newDeps.length = 0;  //將newDeps清空,以備下次更新使用
    }; 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM