vue 響應式基本實現 (基於 Object.defineProperty )

前言:該篇文章是vue響應式的基本實現,包括對對象,數組的處理,以及對部分數組方法的重寫。

        vue的響應式原理實現 ==>> 基於Object.defineProperty 實現

特點:

        1. 使用對象的時候 必須先聲明屬性 ,這個屬性纔是響應式的

        2. 增加不存在的屬性 不能更新視圖 (需要使用 vm.$set  ==> 該文章中沒有實現)

        3. 默認會遞歸增加 get 和 set 屬性

        4. 數組裏套對象 對象是支持響應式變化的,如果是常量則沒有效果

        5. 如果新增的數據 vue中也會幫你監控(對象類型)

        6. 修改數組索引和長度 是不會導致視圖更新的

代碼實現:

let arrayProto = Array.prototype;         //保存下數組原型上的方法
let newProto = Object.create(arrayProto);       //以 數組原型上的方法爲原型 創建新的對象 即 newProto.__proto__ = arrayProto
['push', 'unshift', 'splice', 'reverse', 'sort', 'shift', 'pop'].forEach(method => {
    // 'push', 'unshift', 'splice' ==>> 這三個方法會給數組新增元素,所以需要先監控新增的元素,在調用本身的方法
    // 'reverse','sort','shift','pop' ==>> 這幾個方法不會新增元素,所以只需要調用數組本身對應的方法即可
    newProto[method] = function(...args) {
        let inserted = null;         //默認沒有插入新的數據,即沒有調用 'push', 'unshift', 'splice'
        switch (method){
            // args 是一個數組
            case 'push':
                inserted = args;
                break;
            case 'unshift':
                inserted = args;
                break;
            case 'splice':   //splice方法有三個參數,第三個參數纔是被新增的元素
                inserted = args.slice(2);       //slice返回一個新的數組
                break;
        }
        console.log('數組類型導致視圖更新')
        if (inserted) ArrayObserver(inserted);      //因爲inserted是一個數組,所以要調用數組處理方法 ArrayObserver
        arrayProto[method].call(this, ...args);     //調用數組本身對應的方法
    }
})

function observer(obj){
    if(typeof obj !== 'object' || obj === null){      //obj是常量
        return obj;
    }
    if(Array.isArray(obj)){         //obj是數組
        Object.setPrototypeOf(obj,newProto); // 對數組部分方法進行重寫;==>> obj.__proto__ = newProto
        ArrayObserver(obj);
    }else{          // obj 是對象
        for (const key in obj) {
            // 默認obj只有一層,即 {name:'張三'}
            defineReactive(obj, key, obj[key]);
        }
    }
}

function ArrayObserver(obj){        //處理數組類型的數據
    obj.forEach(item => {
        observer(item);  //監控每一個數組對象,如果是對象,會被defineReactive,而如果是常量,則被直接返回(不監控)
    });
}

// 給對象添加get/set屬性 ===>> 實現響應式原理
function defineReactive(obj, key, value){
    observer(value);        //value有可能也是一個對象,所以也需要響應式  ==>> 缺點:在數據層級較深的情況下 遞歸創建 響應式數據,性能不好
    Object.defineProperty(obj, key, {       //給obj的key屬性添加get/set
        get(){
            // 注意:這裏不能返回 obj[key],會導致死循環(get中取值,又調用get....)
            //      ==>> RangeError: Maximum call stack size exceeded
            return value;
        },
        set(newValue){
            if(value !== newValue){     //如果新值和舊值是一樣的,沒必要更新
                observer(newValue);     //新值有可能也是一個對象,但如果是常量,會在 observer() 直接被返回
                value = newValue;       //這裏也不能使用 obj[key] ==>> 調用get(),不能使用它的返回值去重新賦值
                console.log('視圖更新')
            }
        }
    })
}

// ================== 測試代碼 =================
// 多層對象
// let data = {name: {d: '123'}};
// observer(data);
// data.name.d = 'abc';
// console.log('===',data.name.d);
// ===============

// 使用對象的時候 必須先聲明屬性 ,這個屬性纔是響應式的
// let data = {name: {d: '123'}};
// observer(data);
// data.name = {age: 18}
// data.name.age = 20;
// console.log('====',data.name.age)

// ===============

// 數組裏套對象 對象是支持響應式變化的,如果是常量則沒有效果
let data = {name: [1, 2, 3, {age : 18}]};
observer(data);
// data.name[3].age = 30;
// data.name.splice(1, 0, 30);
data.name.push({a: 'abc'});
data.name[4].a = 'aaa'
console.log('==修改後的值',data.name[4].a)

補充:看一下 Object.create 在瀏覽器中的效果;

        可參考原型鏈的文章:ES5中對Object擴展的靜態方法 -- Object.create可以實現原型繼承(繼承實現方法二,不常用)

                                            JS 的原型和原型鏈 以及 instanceof 是如何判斷的(繼承實現方法一,最基礎,底層實現)

 

文章僅爲本人學習過程的一個記錄,僅供參考,如有問題,歡迎指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章