【筆記】Vue-7 - v3版本實現響應式

Vue3.0 響應式由 Object.defineProperty 改成 Proxy 實現
由於前者沒法監聽到對象上增刪屬性的變化html

Vue3.0的響應式用到了Proxy和Reflect兩個ES6新增的功能es6

Proxy

Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。數組

Reflect

Reflect對象與Proxy對象同樣,也是 ES6 爲了操做對象而提供的新 API。Reflect對象的設計目的有這樣幾個。app

  1. Object對象的一些明顯屬於語言內部的方法(好比Object.defineProperty),放到Reflect對象上。
  2. 修改某些Object方法的返回結果,讓其變得更合理。好比,Object.defineProperty(obj, name, desc)在沒法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false
  • Reflect.get(target, name, receiver)
    Reflect.get方法查找並返回target對象的name屬性,若是沒有該屬性,則返回undefined
  • Reflect.set(target, name, value, receiver)
    Reflect.set方法設置target對象的name屬性等於value

createReactive函數中改成使用Proxy異步

let createReactive = (target, prop, value) => {
    target._dep = new Dep()
    return new Proxy(target, {
      get(target, prop) {
        target._dep.depend()
        return Reflect.get(target, prop)
      },
      set(target, prop, value) {
        target._dep.notify()
        return Reflect.set(target, prop, value)
      }
    })
  }

示例:函數

  1. 直接監聽對象新增的屬性
  2. 去掉push方法的處理,直接使用

均可以成功this

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="add">add</button>
  <div id="app"></div>
  <hr>
  <button id="addArr">addArr</button>
  <div id="appArr"></div>
</body>
<script>
  let active

  let effect = (fn, options = {}) => {
    // 爲何要增長一個_effect函數
    // 由於須要給_effect增長屬性
    // 也能夠直接給fn增長,可是因爲引用類型的緣由,會對fn函數形成污染
    let _effect = (...args) => {
      try {
        active = _effect
        return fn(...args)
      } finally {
        active = null
      }
    }

    _effect.options = options
    _effect.deps = [] // effect和dep的關係-1
    return _effect
  }

  let cleanUpEffect = (effect) => {
    // 清除依賴
    // 須要反向查找effect被哪些dep依賴了
    // 在effect上添加[] 創建雙向索引
    const { deps } = effect
    console.log(deps)
    console.log(effect)
    if (deps.length) {
      for (let i = 0; i < deps.length; i++) {
        deps[i].delete(effect)
      }
    }
  }

  let watchEffect = (cb) => {
    /* active = cb
    active()
    active = null */
    let runner = effect(cb)
    runner()

    return () => {
      cleanUpEffect(runner)
    }
  }

  let nextTick = (cb) => Promise.resolve().then(cb)

  // 隊列
  let queue = []

  // 添加隊列
  let queueJob = (job) => {
    if (!queue.includes(job)) {
      queue.push(job)
      // 添加以後,將執行放到異步任務中
      nextTick(flushJob)
    }
  }

  // 執行隊列
  let flushJob = () => {
    while (queue.length > 0) {
      let job = queue.shift()
      job && job()
    }
  }


  let Dep = class {
    constructor() {
      // 存放收集的active
      this.deps = new Set()
    }
    // 依賴收集
    depend() {
      if (active) {
        this.deps.add(active)
        active.deps.push(this.deps) // effect和dep的關係-2
      }
    }
    // 觸發
    notify() {
      this.deps.forEach(dep => queueJob(dep))
      this.deps.forEach(dep => {
        dep.options && dep.options.schedular && dep.options.schedular()
      })
    }
  }

  let createReactive = (target, prop, value) => {
    target._dep = new Dep()
    return new Proxy(target, {
      get(target, prop) {
        target._dep.depend()
        return Reflect.get(target, prop)
      },
      set(target, prop, value) {
        target._dep.notify()
        return Reflect.set(target, prop, value)
      }
    })
  }

  let ref = (initValue) => createReactive({}, 'value', initValue)

  const set = (target, prop, initValue) => createReactive(target, prop, initValue)

  let computed = (fn) => {
    let value
    let dirty = true // 爲true代表依賴的變量發生了變化,此時須要從新計算
    let runner = effect(fn, {
      schedular() {
        if (!dirty) {
          dirty = true
        }
      }
    })
    return {
      get value() {
        if (dirty) {
          // 什麼時候將dirty重置爲true,當執行fn後
          // 所以須要經過配置回調函數,在執行fn後將dirty重置爲true
          // value = fn() 
          value = runner()
          dirty = false
        }
        return value
      }
    }
  }

  let watch = (source, cb, options = {}) => {
    const { immediate } = options
    const getter = () => {
      return source()
    }
    // 將函數添加到count的依賴上去,當count變化時
    let oldValue
    const runner = effect(getter, {
      schedular: () => applyCb()
    })

    const applyCb = () => {
      let newValue = runner()
      if (newValue !== oldValue) {
        cb(newValue, oldValue)
        oldValue = newValue
      }
    }

    if (immediate) {
      applyCb()
    } else {
      oldValue = runner()
    }
  }

  // set示例:
  let count = ref(0)
  // count.v新增屬性
  document.getElementById('add').addEventListener('click', function () {
    if (!count.v) {
      count.v = 0
    }
    count.v++
  })

  let str
  let stop = watchEffect(() => {
    str = `hello ${count.v}`
    document.getElementById('app').innerText = str
  })

  // 數組push示例:
  let arrValue = 0
  // set函數中已經對依賴進行了一次添加
  let countArr = set([], 1, 0)
  document.getElementById('addArr').addEventListener('click', function () {
    arrValue++
    countArr.push(arrValue)
  })
  watchEffect(() => {
    str = `hello ${countArr.join(',')}`
    document.getElementById('appArr').innerText = str
  })

</script>

</html>
相關文章
相關標籤/搜索