vue3.0響應式原理

寫在前面

目前,Vue 的反應系統是使用 Object.defineProperty 的 getter 和 setter。 可是,Vue 3 將使用 ES2015 Proxy 做爲其觀察者機制。 這消除了之前存在的警告,使速度加倍,並節省了一半的內存開銷。同時使用新的Composition Api,更好的邏輯複用,類型推導,更高的性能。javascript

2.0的不足

使用遞歸對數據進行劫持,多層嵌套內存消耗大,性能不高。 只能劫持預先設置好的數據,直接添加的數據沒法劫持。能夠經過vue.set(xxx)。 對數組的操做只能是內部劫持的7種方法,直接修改下標不能觸發響應式。html

3.0的提高

  1. 邏輯組合和複用
  2. 類型推導:Vue3.0 最核心的點之一就是使用 TS 重構,以實現對 TS 絲滑般的支持。而基於函數 的 API 則自然對類型推導很友好。
  3. 打包尺寸:每一個函數均可做爲 named ES export 被單獨引入,對 tree-shaking 很友好;其次全部函數名和 setup 函數內部的變量都能被壓縮,因此能有更好的壓縮效率。

3.0的一些基本用法

關於3.0的一些基本api的用法這裏就不詳細介紹,咱們今天着重講一下,reactive和effect的實現,也就是3.0核心的響應式原理是怎麼實現,以及怎樣收集依賴。Composition Apivue

reactive

/** * 這是第一步,實現數據的劫持 */
function reactive(target) {
  return createReactiveObject(target)
}

function createReactiveObject(target) {
  if(!isObject(target)) {
    return target
  }
  // 說明已經代理過了
  const proxyed = toProxy.get(target)
  if (proxyed) {
    return proxyed
  }
  // 防止反覆代理
  // reactive(proxy) reactive(proxy)
  if (toRow.has(target)) {
    return target
  }

  const handles = {
    get(target, key, receiver) {
      let result = Reflect.get(target, key, receiver)
      // 若是是多層次的對象的,咱們須要遞歸代理
      return isObject(result) ? reactive(result) : result
    },
    set(target, key, value, receiver) {
      let oldValue = target[key]
      // 咱們不知道設置是否成功,因此要作一個反射,來告訴咱們是否成功
      let flag = Reflect.set(target, key, value, receiver)
      return flag
    },
    // 刪除的同上
    deleteProperty() {
    }
  }

  const observe = new Proxy(target, handles)
  return observe
}
複製代碼

上面的代碼很簡單,咱們遞歸對咱們的數據實現了劫持,咱們用Reflect反射,這裏set中若是直接用target[key] = value來賦值會報錯,用Reflect能夠返回是否set成功。 這裏有幾個問題咱們須要解決:java

  1. 屢次重複代理同一個對象
let name = {a:123}
reactive(name)
reactive(name)
reactive(name)
複製代碼
  1. 代理過的對象屢次代理
let name = {a:123}
let proxy = reactive(name)
reactive(proxy)
reactive(proxy)
複製代碼

爲了解決上面的倆個問題,源碼裏面用倆個WeakMap來作映射表關係的。因此上面的代碼咱們增長以下代碼。一樣WeakMap也是es6中的,不知道的同窗能夠去看看。WeakMapreact

const toProxy = new WeakMap() // 用來放 當前對象:代理過的對象
const toRow = new WeakMap() // 用來放 代理過的對象: 當前對象


// 說明已經代理過了
const proxyed = toProxy.get(target)
if (proxyed) {
  return proxyed
}
// 防止反覆代理
// reactive(proxy) reactive(proxy)
if (toRow.has(target)) {
  return target
}
// 對已經代理過的,進行保存
toProxy.set(target, observe)
toRow.set(observe, target)
複製代碼

對數組的特殊處理

咱們知道在2.0中對數組咱們只能調用特定的7個方法才能讓數據是響應式的。但在3.0中用Proxy咱們能直接監聽到數組的變換。git

/** * 這裏是數組的一個處理,若是push[1,2] => [1,2,3] * 這裏會觸發兩次的set,一次是下標2的set,一次是length的set * 可是length的set的觸發在這裏是無心義的,length的修改並不須要是響應式的 * oldValue !== value 能夠規避length的修改帶來的影響 */
if (!isOwnProperty(target, key)) {
  console.log('設置新的屬性')
// 修改屬性
} else if (oldValue !== value) {
  console.log('修改原有的屬性')
}
複製代碼

####effect以及依賴收集 下面咱們來實現響應式原理,在vue3.0中effect是一個很是有用的api,它會首先執行一次,而後依賴的數據改變了會自動在執行傳入的函數,至關於computed。(我是這麼理解的)es6

// 棧數組,先進後出
/** * 依賴收集 (數據: [effect]) * 每一個數據對應它的依賴,數據一變執行方法 */
const activeEffectStacks = []

/** * 創建依賴關係 * 數據結構 * * (WeakMap): { * target: (Map) { * key: (Set) [effect,effect] * } * } */
const targetMap = new WeakMap()
function track(target, key) {
  let effect = activeEffectStacks[activeEffectStacks.length - 1]
  if (effect) {
    let depsMap = targetMap.get(target)

    if (!depsMap) {
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }

    let deps = depsMap.get(key)
    if (!deps) {
      deps = new Set()
      depsMap.set(key, deps)
    }
    if (!deps.has(effect)) {
      deps.add(effect)
    }
  }
}

/** * 第二步,實現數據的響應式 * 數據變通知依賴的數據更新 * * 反作用,先會執行一次,當數據變話的時候在執行一次 * 這裏面設計到一個依賴收集的東西,源碼裏面用一個棧(數組[])來作的 * */
function effect(fn) {
  const effectFun = createReactiveEffect(fn)
  effectFun()
}
function createReactiveEffect(fn) {
  const effect = function() {
    run(effect, fn)
  }
  return effect
}

function run(effect, fn) {
  try {
    // 棧裏面已經拿到數據了之後,清掉保證數據量
    // try 保證fn執行報錯時,必定能將棧清除
    activeEffectStacks.push(effect)
    fn()
  } finally{
    activeEffectStacks.pop(effect)
  }
}

// 這裏增長依賴收集
get(target, key, receiver) {
	let result = Reflect.get(target, key, receiver)
	// 進行依賴收集
	/** * 這裏很巧妙,在第一次調用effect的時候,必定能觸發一次target的get方法的 * 此時咱們將依賴的關係創建 */
	track(target, key)
	// 若是是多層次的對象的,咱們須要遞歸代理
	return isObject(result) ? reactive(result) : result
},
複製代碼

這裏咱們主要將一下這個收集依賴的數據結構關係。用一個weakMap來放[target, Map], Map[key, Set],Set[effect],這樣咱們就能創建一個target,key,effect的依賴關係,每次target[key]改變的時候咱們就能將對應的effect循環執行一遍。github

trigger 依賴觸發

/** * 依賴的觸發 */
function trigger(target, type, key) {
  // 這裏先不作type的區分
  const depsMap = targetMap.get(target)
  if (depsMap) {
    const deps = depsMap.get(key)
    if (deps) {
      deps.forEach(effect => {
        effect()
      })
    }
  }
}
// 咱們在get的時候觸發依賴
if (!isOwnProperty(target, key)) {
   trigger(target, 'add', key)
   console.log('設置新的屬性')
 // 修改屬性
 } else if (oldValue !== value) {
   trigger(target, 'set', key)
   console.log('修改原有的屬性')
 }
複製代碼

好了,這樣咱們一個完整的一個數據劫持,依賴收集,依賴觸發就基本完成。 完整代碼api

相關文章
相關標籤/搜索