數據響應式原理 - 01

這是我參與更文挑戰的第3天,活動詳情查看: 更文挑戰vue

本文是關於數據響應式原理的學習筆記,目的在於更好的理解 Vue 的底層原理,篇幅較長,故而拆分爲幾篇,從此會陸續更新~react

Object.defineProperty()

得力於 Object.defineProperty() 的特性,vue 的數據變化有別於 react 和小程序,是非侵入式的。詳細介紹能夠看 MDN 文檔,這裏特別說明幾點:c++

  1. get / set 屬性是函數,出於習慣會被稱爲 getter 函數 / setter 函數(Java,c++ 中都有這種慣例)
  2. value 或 writableget 或 set 是不能同時出現的,不然報錯
  3. 注意區別 Object.defineProperties()

定義 defineReactive 函數

Object.defineProperty() 在使用 getter 和 setter 的時候,要想實現屬性的修改,須要藉助一個變量週轉,以下面的 value,這就很麻煩。小程序

const obj = {}
let value
Object.defineProperty(obj, 'a', {
  enumerable: true,
  configurable: true,
  get() {
    console.log('getter')
    return value
  },
  set(newValue) {
    value = newValue
    console.log('setter', newValue)
  }
})
複製代碼

因此定義了 defineReactive 函數,方便去給對象增長一個響應式屬性。這裏建立一個閉包的環境:閉包必定要有內外兩個函數,外面這個函數 defineReactive 的 value 就造成了閉包。數組

const obj = {}
function defineReactive(data, key, value) {
  // 若是隻傳了兩個參數,則讓 value 直接等於 data[key]
  if (arguments.length === 2) value = data[key]
  
  Object.defineProperty(data, key, {
    enumerable: true, // 可被枚舉(for...in 或 Object.keys 方法)
    configurable: true, // 可被配置,好比刪除
    get() {
      console.log('查看了' + key + '屬性')
      return value
    },
    set(newValue) {
      console.log('修改了' + key + '屬性')
      value = newValue
    }
  })
}

defineReactive(obj, 'a', 10)
console.log(obj.a)
obj.a = 11
console.log(obj.a)
複製代碼

獲得的結果以下圖markdown

image.png

遞歸偵測對象的所有屬性

咱們本身寫一個可以偵測對象所有屬性的庫
新建 index.js 做爲主入口文件,用於測試效果,咱們 let 一個對象 obj,目標是經過把 obj 做爲參數傳給 observe 函數,便可實現對 obj 對象全部屬性的查看與修改的監測。閉包

// index.js
import observe from './observe.js'
let obj = {
  a: {
    m: {
      n: 1
    }
  },
  b: 2
}
observe(obj)
複製代碼

流程分析

yuque_diagram.jpg

observe 函數

observe 函數用於觀察一個對象(value)的屬性是否已被監測的(是否有 __ob__ 屬性),若是不是則讓其屬性成爲響應式的(經過 new Observer(value))。
注意:之因此起了 __ob__ 這麼奇怪的變量名,是爲了保證不會與對象的原有屬性同名。函數

// observe.js
import Observer from './Observer.js'
export default (value) => {
  if (typeof value !== 'object') return
  if (value.__ob__ !== undefined) {
    // 暫時留空
  } else {
    new Observer(value)
  }
}
複製代碼

Observer 類

Observer 是一個類,一旦 new 了一個實例,則作 2 件事:oop

  1. 給傳入的 value(實際上是個對象) 添加 __ob__ 屬性,值爲此次 new 的實例(也就是構造函數中的 this),由於但願 __ob__ 屬性是不可被枚舉的,因此用 def 函數處理。
  2. 遍歷 value 的屬性,經過 defineReactive 函數將其變爲響應式的
// Observer.js
import { def } from './utils.js'
import defineReactive from './defineReactive.js'

export default class Observer {
  constructor(value) {
    def(value, '__ob__', this, false)
    this.walk(value)
  }
  // 處理對象,讓對象的屬性變爲響應式
  walk(value) {
    for (let key in value) {
      defineReactive(value, key)
    }
  }
}
複製代碼

def 函數定義以下post

export const def = (obj, key, value, enumerable) => {
  Object.defineProperty(obj, key, {
    value,
    enumerable,
    writable: true,
    configurable: true
  })
}
複製代碼

完善 defineReactive 函數

相較於前面定義的時候,在兩個地方添加了 observe(value),從而實現了遞歸偵測對象的所有屬性。這裏的參數 value,就是已經被變爲響應式的屬性的值,這個值若是是個對象,也須要被偵測,因此也要被 observe。

// defineReactive.js
import observe from './observe.js'

export default function defineReactive(data, key, value) {
  if (arguments.length === 2) value = data[key]
  
  // 注意這裏不是傳 key 而是傳 value,由於 key 只是一個字符串,value 纔是 key 指向的對象
  observe(value)
  
  // 讓 data 的 key 屬性變爲響應式屬性
  Object.defineProperty(data, key, {
    enumerable: true, 
    configurable: true, 
    get() {
      console.log('查看了' + key + '屬性')
      return value
    },
    set(newValue) {
      console.log('修改了' + key + '屬性')
      value = newValue
      // 修改的屬性也須要被觀察,若是是對象須要被偵測
      observe(newValue)
    }
  })
}
複製代碼

至此,在 index.js 傳入 observe 的 obj 的每一個屬性都是響應式的了

// index.js
...省略前面的代碼
obj.a.m = {
  y: 8
}
console.log(obj.a.m.y)
複製代碼

測試結果以下

image (1).png

接下去就是關於數組的響應式處理,但爲了避免讓每篇文章的篇幅過長以致於讀起來昏昏欲睡,將在下篇繼續分享~

One More Thing

普通對象也是有 getter 和 setter 的:

  • get propertyName(){} 用來獲得當前屬性值的回調函數
  • set propertyName(){} 用來監視當前屬性值變化的回調函數
  • 下面的代碼中,屬性 a 稱爲「數據屬性」,它只有一個簡單的值;屬性b這種用 getter 和 setter 方法定義的屬性稱爲「存取器屬性」。
var num= {
    a: 2,
    get b(){
        return 2
    }   
}
複製代碼

存取器屬性定義爲一個或兩個與屬性同名的函數,這個函數定義不使用 function 關鍵字,而是使用 get 或 set,也沒有使用冒號將屬性名和函數體分開。

感謝.gif

點贊.png

相關文章
相關標籤/搜索