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

相关文章
相关标签/搜索