Vue響應式數據原理2.0

Object.defineProperty

數據模型僅僅是普通的 JavaScript 對象,可是對這些對象進行操做時,卻能影響對應視圖,簡而言之,就是你動我也動。 它的核心實現就是「響應式系統」,核心內容爲Object.defineProperty 使用方法以下:編程

/* obj: 目標對象 prop: 目標對象的屬性名 descriptor: 描述符 return value 傳入對象 */
Object.defineProperty(obj, prop, descriptor)
複製代碼

descriptor的一些屬性數組

  • enumerable,屬性是否可枚舉,默認 false。
  • configurable,屬性是否能夠被修改或者刪除,默認 false。
  • get,獲取屬性的方法。
  • set,設置屬性的方法。

實現 observer(可觀察到你動了)

首先定義一個假的函數來模擬更新app

function updateView(val) {
    /* 僞裝是視圖 */
    console.log("我動了");
}
複製代碼

而後咱們定義一個 defineReactive ,這個方法經過 Object.defineProperty 來實現對對象的「響應式」化,通過 defineReactive 處理之後,咱們的 target 的 key 屬性在「」的時候會觸發 get 方法,而在該屬性被「」的時候則會觸發 set 方法。函數

function defineReactive (target, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            return val;
        },
        set(newVal) {
            if (newVal === val) return;
            updateView(newVal);
        }
    });
}
複製代碼

這樣貌似ok了,可是沒人讓他動起來,咱們再封裝一層observer,這個函數傳入一個 value(須要「響應式」化的對象),經過遍歷全部屬性的方式對該對象的每個屬性都經過 defineReactive 處理。測試

function observer (target) {
    if (!target || (typeof target !== 'object')) {
        return;
    }
    
    Object.keys(target).forEach((key) => {
        defineReactive(target, key, target[key]);
    });
}
複製代碼

最後爲了好看點,封裝一個Vueui

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}
// 測試
let o = new Vue({
    data: {
        test: "I am test."
    }
});
o._data.test = "hello,world.";  /* 我動了 */
複製代碼

問題來了

1,這隻測試了字符串,要是測試嵌套對象就很容易發現問題,不動啦,原理也很簡單,指向同一內存,能夠類比深淺拷貝

那麼咱們要進行遞歸調用this

function defineReactive(target, key, value){
    observer(value); // 遞歸 我就將這個對象 繼續攔截
    Object.defineProperty(target,key,{
        get(){
            return value 
        },
        set(newValue){
            if(newValue !== value){ // 不一樣值才更新
                // o_data.age = {n:200};o _data.age.n = 300;這種狀況就須要從新觀察
                observer(newValue)
                updateView();
                value = newValue
            }
        }
    });
}
複製代碼

2,新加數組項不會觸發更新

改寫observer, 爲了讓不改變原數組,巧妙運用切片編程spa

let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 繼承
['push','shift','unshift'].forEach(method=>{
    proto[method] = function(){ //函數劫持 把函數進行重寫 內部 繼續調用老的方法
        updateView(); // 切片編程
        oldArrayPrototype[method].call(this, ...arguments)
        // oldArrayPrototype[method].apply(this, arguments)
    }
});
function observer(target){
    if(typeof target !== 'object' || target == null){
        return target;
    }
    if(Array.isArray(target)){ // 攔截數組 給數組的方法進行了重寫 
        Object.setPrototypeOf(target,proto); // 寫個循環 賦予給target
        // target.__proto__ = proto;
        for(let i = 0; i< target.length ;i++){
            observer(target[i]);
        }
    }else{
        Object.keys(target).forEach((key) => {
            defineReactive(target, key, target[key]);
        });
    }
   
}
複製代碼

3,新增的屬性不更新,使用$set啦

整合代碼以下

let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 繼承
['push','shift','unshift'].forEach(method=>{
    proto[method] = function(){ //函數劫持 把函數進行重寫 內部 繼續調用老的方法
        updateView(); // 切片編程
        oldArrayPrototype[method].call(this,...arguments)
    }
});
function observer(target){
    if(typeof target !== 'object' || target == null){
        return target;
    }
    if(Array.isArray(target)){ // 攔截數組 給數組的方法進行了重寫 
        Object.setPrototypeOf(target,proto); // 寫個循環 賦予給target
        // target.__proto__ = proto;
        for(let i = 0; i< target.length ;i++){
            observer(target[i]);
        }
    }else{
        Object.keys(target).forEach((key) => {
            defineReactive(target, key, target[key]);
        });
    }
   
}
function defineReactive(target,key,value){
    observer(value); // 遞歸 我就將這個對象 繼續攔截
    Object.defineProperty(target,key,{
        get(){ // get 中會進行依賴收集
            return value 
        },
        set(newValue){
            if(newValue !== value){
                // data.age = {n:200}; data.age.n = 300;這種狀況就須要從新觀察
                observer(newValue) 
                updateView()
                value = newValue
            }
        }
    });
}
function updateView(){
    console.log('我動啦')
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}
// 測試
let o = new Vue({
    data: {
        test: "I am test."
    }
});
o._data.test = "hello,world.";  /* 我動了 */

複製代碼
相關文章
相關標籤/搜索