1. 程式人生 > 其它 >Vue2/3響應式原理實現

Vue2/3響應式原理實現

響應式原理

1.什麼是響應式

監聽物件中的屬性被設定或獲取的過程,當物件屬性變化時能夠監聽到,併發出通知

2.響應式函式的封裝

const reactiveFns = []
watchFn(fn) {  }

3.Depend 類的封裝

class Depend {
  constructor() {
    this.reactiveFns = []
  }

  addDepend(reactiveFn) {
    this.reactiveFns.push(reactiveFn)
  }

  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

4. 監聽物件的變化

new Proxy(, set: depend.notify())

5. 依賴收集的資料結構

WeakMap

function getDepend() {}

6. 正確的收集依賴

Proxy的get方法中收集對應的函式
全域性activeReactiveFn變數
在get中找到depend物件, addDepend(全域性activeReactiveFn變數)

7. 對Depend進行優化

addDepend函式換成depend函式
直接獲取到自由變數
將之前儲存的陣列[]變成Set

8. 對物件的響應式操作

封裝reactive函式

  • new Proxy(): Vue3
  • Object.defineProperty(): Vue2

vue3響應式實現:

// 儲存當前需要收集的響應式函式
let activeReactiveFn = null

/**
 * Depend優化:
 *  1> depend方法
 *  2> 使用Set來儲存依賴函式, 而不是陣列[]
 */

class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }

  // addDepend(reactiveFn) {
  //   this.reactiveFns.add(reactiveFn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

// 封裝一個響應式的函式
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封裝一個獲取depend函式
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根據target物件獲取map的過程
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  // 根據key獲取depend物件
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

function reactive(obj) {
  return new Proxy(obj, {
    get: function(target, key, receiver) {
      // 根據target.key獲取對應的depend
      const depend = getDepend(target, key)
      // 給depend物件中新增響應函式
      // depend.addDepend(activeReactiveFn)
      depend.depend()
  
      return Reflect.get(target, key, receiver)
    },
    set: function(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver)
      // depend.notify()
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}

// 監聽物件的屬性變數: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
  name: "why", // depend物件
  age: 18 // depend物件
})

const infoProxy = reactive({
  address: "廣州市",
  height: 1.88
})

watchFn(() => {
  console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
  name: "foo"
})

watchFn(() => {
  console.log(foo.name)
})

foo.name = "bar"


vue2響應式實現:

// 儲存當前需要收集的響應式函式
let activeReactiveFn = null

/**
 * Depend優化:
 *  1> depend方法
 *  2> 使用Set來儲存依賴函式, 而不是陣列[]
 */

class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }

  // addDepend(reactiveFn) {
  //   this.reactiveFns.add(reactiveFn)
  // }

  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }

  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

// 封裝一個響應式的函式
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封裝一個獲取depend函式
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根據target物件獲取map的過程
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }

  // 根據key獲取depend物件
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

function reactive(obj) {
  // {name: "why", age: 18}
  // ES6之前, 使用Object.defineProperty
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function() {
        const depend = getDepend(obj, key)
        depend.depend()
        return value
      },
      set: function(newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend.notify()
      }
    })
  })
  return obj
}

// 監聽物件的屬性變數: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
  name: "why", // depend物件
  age: 18 // depend物件
})

const infoProxy = reactive({
  address: "廣州市",
  height: 1.88
})

watchFn(() => {
  console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
  name: "foo"
})

watchFn(() => {
  console.log(foo.name)
})

foo.name = "bar"
foo.name = "hhh"