3分鐘瞭解vue數據劫持的原理

目的: 瞭解Object.defineProperty如何實現數據劫持javascript

閱讀時間: 3 分鐘前端

大體原理是這樣的:vue

  1. 定義一個監聽函數,對對象的每個屬性進行監聽
  2. 經過Object.defineProperty對監聽的每個屬性設置get 和 set 方法。
  3. 對對象實行監聽
  4. 對對象內嵌對象進行處理
  5. 對數組對象進行處理

1. 先定義一個對象java

let obj = {
  name: 'jw'
}
複製代碼

2. 定義一個監聽函數數組

/** * 判斷監聽的是不是對象 * 若是是對象,就遍歷,而且對每一個屬性進行定義get 和 set */

function observer(obj) {
  if(typeof obj === 'object') {
    for (let key in obj) {
    // defineReactive 方法設置get和set,見第三步
      defineReactive(obj, key, obj[key]);
    }
  }
}
複製代碼

3.定義一個函數,處理每一個屬性函數

function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    get() {
      return value;
    },
    set(val) {
      console.log('數據更新了')
      value = val;
    }
  })
}
複製代碼

ok. 到這裏第一版已經實現了。嘗試一下吧ui

observer(obj);
obj.name = 'haha'

控制檯輸出: 
//數據更新了
複製代碼

以上已經實現設置obj的屬性的時候,被監聽到,而且能夠去執行一些代碼了。可是,若是對象裏面嵌入了對象呢?好比: this

let obj = {
  name: 'jw',
  age: {
    old: 60
  }
}
複製代碼

執行如下代碼spa

observer(obj);
obj.age.old = '50'

控制檯輸出: 空
複製代碼

4.對監控的obj進行迭代處理prototype

// 修改defineReactive , 添加一行代碼
function defineReactive(obj, key, value) {
  // 若是對象的屬性也是一個對象。迭代處理
  observer(value);
  Object.defineProperty(obj, key, {
    //....
  })
}
複製代碼

再執行如下代碼:

observer(obj);
obj.age.old = '50'

控制檯輸出:
//數據更新了
複製代碼

惋惜的是,若是對象是一個數組,Object.defineProperty就沒法起做用了,好比:

obj.skill = [1, 2, 3];
obj.skill.push(4);
控制檯輸出: 
//空
複製代碼

實際上,不止push,包括slice,shift,unshif...都是沒有做用.

5. 重寫數組的方法

let arr = ['push', 'slice', 'shift', 'unshift'];
arr.forEach(method=> {
  let oldPush = Array.prototype[method];
  Array.prototype[method] = function(value) {
    console.log('數據更新了')
    oldPush.call(this, value)
  }
})
複製代碼

再執行如下代碼:

obj.skill = [1, 2, 3];
obj.skill.push(4);


控制檯輸出:
//數據更新了
複製代碼

可是,數組的length操做仍然是無效的。這也是爲何vue中只能經過方法去改變數組的緣由了。

總結: Object.defineProperty只是解決了狀態變動後,如何觸發通知的問題,那要通知誰呢?誰會關心那些屬性發生了變化呢?之後再說。

感謝閱讀!

我是海明月, 前端小學生。


如下完整代碼

let obj = {
  name: 'jw',
  age: {
    old: '60'
  }
}

// vue 數據劫持 Observer.defineProperty

function observer(obj) {
  if(typeof obj === 'object') {
    for (let key in obj) {
      defineReactive(obj, key, obj[key]);
    }
  }
}

function defineReactive(obj, key, value) {
  observer(value);

  Object.defineProperty(obj, key, {
    get() {
      return value;
    },
    set(val) {
      console.log('數據更新了')
      value = val;
    }
  })
}
observer(obj);


// obj.age.old = '50'


// Object.defineProperty 對 數組無效
let arr = ['push', 'slice', 'shift', 'unshift'];

arr.forEach(method=> {
  let oldPush = Array.prototype[method];
  Array.prototype[method] = function(value) {
    console.log('數據更新了')
    oldPush.call(this, value)
  }
})
obj.skill = [1, 2, 3];
obj.skill.push(4);
複製代碼
相關文章
相關標籤/搜索