.3-Vue源碼之數據劫持(1)


寫了一半關機了,又得重新寫,好氣。

 

  上一節講到initData函數,其中包含格式化、代理、監聽。

    // Line-3011
    function initData(vm) {
        var data = vm.$options.data;
        //data = vm._data = ... 格式化data
        // ...proxy(vm, "_data", keys[i]); 代理
        // 監聽
        observe(data, true /* asRootData */ );
    }

  這一節重點開始跑observe函數,該函數接受2個參數,一個是數據,一個布爾值,代表是否是頂層根數據。

    // Line-899
    function observe(value, asRootData) { if (!isObject(value)) { return } var ob; // 判斷是否有__ob__屬性 即是否已被監聽
        if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__; } // 若無進行條件判斷 
        else if ( observerState.shouldConvert && // 是否應該被監聽 默認為true
            !isServerRendering() && // 是否是服務器渲染
            (Array.isArray(value) || isPlainObject(value)) && // 數據必須為數組或對象
            Object.isExtensible(value) && // 是否可擴展 => 能添加新屬性
            !value._isVue // vue實例才有的屬性
 ) { // 生成一個觀察者
            ob = new Observer(value); } // 根數據記數屬性++
        if (asRootData && ob) { ob.vmCount++; } return ob }

  observe函數除去大量的判斷,關鍵部分就是new了一個觀察者來進行數據監聽,所以直接跳進該構造函數:

    // Line-833
    var Observer = function Observer(value) { // data
        this.value = value; // 依賴收集
        this.dep = new Dep(); this.vmCount = 0; // 通過Object.defineProperty定義__ob__屬性 this指向Observer實例
        def(value, '__ob__', this); // 根據類型調用不同的遍歷方法
        if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } };

  這個構造函數給實例綁了3個屬性,分別為data對象的value、記數用的vmCount、依賴dep,接着根據數據類型調用不同的遍歷方法進行依賴收集。  

  Dep對象比較簡單,包含2個屬性和4個對應的原型方法,如下:

    // Line-720
    // 超級簡單
    var Dep = function Dep() {
        this.id = uid++;
        this.subs = [];
    };
    // 原型方法
    Dep.prototype.addSub = function addSub(sub) {
        this.subs.push(sub);
    };
    Dep.prototype.removeSub = function removeSub(sub) {
        remove(this.subs, sub);
    };
    Dep.prototype.depend = function depend() {
        if (Dep.target) {
            Dep.target.addDep(this);
        }
    };
    Dep.prototype.notify = function notify() {
        // stabilize the subscriber list first
        var subs = this.subs.slice();
        for (var i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    };

  可見,new出來的dep實例只有2個屬性,一個是每次+1的計數uid,還有一個是依賴收集數組。

  原型上的4個方法分別對應增、刪、添加依賴、廣播,由於暫時用不到update函數,所以先放着。

  

  接着,用自定義的def方法把__ob__屬性綁到了生成的observe實例上,該屬性引用了自身。

 

  最后,根據value是類型是數組還是對象,調用不同的方法進行處理。案例中傳進來的value是一個object,所以會跳到walk方法中。

  這里不妨看看如果是數組會怎樣。

    // Line-838
    if (Array.isArray(value)) {
        // 判斷是否支持__proto__屬性
        var augment = hasProto ?
            protoAugment :
            copyAugment;
        // 原型擴展
        augment(value, arrayMethods, arrayKeys);
        // 數組監聽方法
        this.observeArray(value);
    }

  其中,根據環境是否支持__proto__分別調用protoAugment或copyAugment,這兩個方法比較簡單,上代碼就能明白。

    // Line-876
    function protoAugment(target, src) {
        // 直接指定原型
        target.__proto__ = src;
    }
    
    // Line-887
    function copyAugment(target, src, keys) {
        // 遍歷keys
        // 調用def(tar,key,value) => (tar[key] = (value => src[key]))
        for (var i = 0, l = keys.length; i < l; i++) {
            var key = keys[i];
            def(target, key, src[key]);
        }
    }

  選擇了對應的方法就開始調用,傳進的參數除了value還是兩個奇怪的值:arrayMethods、arrayKeys。

    // Line-767
    // 創建一個對象 原型為數組對象的原型
    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);
    console.log(Array.isArray(arrayMethods)); //false
    console.log('push' in arrayMethods); //true
    
    // Line-814
    var arrayKeys = Object.getOwnPropertyNames(arrayMethods);

  簡單來講,arrayMethods就是一個對象,擁有數組的方法但不是數組。

  Object.getOwnPropertyNames方法以數組形式返回對象所有可枚舉與不可枚舉方法,所以arrayKeys直接在控制台打印可以看到:

 

  最后,不管選擇哪個方法,都會將“改造過”的數組方法添加到value對象上,由於代碼跑不到,等下次給出具體值吧。這里接着會調用observeArray方法,將數組value穿進去。

    // Line-865
    Observer.prototype.observeArray = function observeArray(items) {
        // 遍歷分別調用observe方法
        for (var i = 0, l = items.length; i < l; i++) {
            observe(items[i]);
        }
    };

  繞了一圈,最后還是遍歷value,挨個調用observe方法,並指向了walk方法。

    // Line-855
    Observer.prototype.walk = function walk(obj) {
        // 獲取對象的鍵
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) {
            // 核心方法
            defineReactive$$1(obj, keys[i], obj[keys[i]]);
        }
    };

  這個方法比較簡單,獲取傳進來的對象鍵,遍歷后調用defineReactive$$1方法。看名字也就差不多明白了,這是響應式的核心函數,雙綁爸爸。

  下節再來說這個,完結完結!  

 

 

補充tips:

  之前有一段代碼,我說將改造過的數組方法添加到數組value上,這個改造是什么意思呢?其實關於arrayMethods代碼沒有全部貼出來,這里做簡單的解釋。

    // Line-767
    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);
    // 以下數組方法均會造成破壞性操作
    [
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'
    ]
    .forEach(function(method) {
        // 緩存原生方法
        var original = arrayProto[method];
        // 修改arrayMethods上的數組方法
        def(arrayMethods, method, function mutator() {
            // 將argument轉換為真數組
            var arguments$1 = arguments;
            var i = arguments.length;
            var args = new Array(i);
            while (i--) {
                args[i] = arguments$1[i];
            }
            // 首先執行原生方法
            var result = original.apply(this, args);
            var ob = this.__ob__;
            var inserted;
            // 值添加方法
            // push和unshift會添加一個值 即args
            // splice(a,b,c,..)方法只有c后面是添加的值 所以用slice排除前兩個參數
            switch (method) {
                case 'push':
                    inserted = args;
                    break
                case 'unshift':
                    inserted = args;
                    break
                case 'splice':
                    inserted = args.slice(2);
                    break
            }
            // 對添加的值調用數組監聽方法
            if (inserted) {
                ob.observeArray(inserted);
            }
            // 廣播變化 提示DOM更新及其他操作
            ob.dep.notify();
            return result
        });
    });

  完整的arrayMethods如上所述,解釋大部分都寫出來了,這也是vue通過對數組方法的劫持來達到變化監聽的原理,對象的劫持下節再來分析。

 

  慣例,來一張圖:

 

 

  快撒花!


免責聲明!

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



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