vue3响应式原理附

上面讲了的N多源码。。其实只讲了怎么实现的,没有讲为什么要这么实现。

vue3为什么会采用不同的响应式原理:

这是因为vue2使用的Object.defineProperty存在一些问题:

1.为什么vue2.x不支持使用数组下标来响应数据变化,只能用$set?

事实上,Object.defineProperty 本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能 / 体验的性价比考虑,放弃了这个特性。 下面是栗子:

function defineReactive(data, key, value) {
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
     get: function defineGet() {
      console.log(`get key: ${key} value: ${value}`)
      return value
    },
     set: function defineSet(newVal) {
      console.log(`set key: ${key} value: ${newVal}`)
      value = newVal
    }
  })
}
function observe(data) {
  Object.keys(data).forEach(function(key) {
    defineReactive(data, key, data[key])
  })
}
let arr = [1, 2, 3]
observe(arr)

push 操作触发不了defineProperty;

unshift会多次触发defineProperty;get和set

pop 对于原有的index,会触发get,否则,不会触发;

  • Object.defineProperty 在数组中的表现和在对象中的表现是一致的,数组的索引就可以看做是对象中的 key。

  • 通过索引访问或设置对应元素的值时,可以触发 getter 和 setter 方法。

  • 通过 push 或 unshift 会增加索引,对于新增加的属性,需要再手动初始化才能被 observe。

  • 通过 pop 或 shift 删除元素,会删除并更新索引,也会触发 setter 和 getter 方法。

所以,Object.defineProperty是有监控数组下标变化的能力的,只是 Vue2.x 放弃了这个特性。

 

对比Object.defineProperty和 Proxy。

Proxy

Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有访问都先经过这层拦截,所以我们叫它为代理模式。

ES6原生提供了Proxy构造函数,用来生成Proxy实例。

var proxy = new Proxy(target, handler);
const target = []
const proxy = new Proxy(target, {
    get: (obj, prop) => {
        console.log('设置 get 操作')
        return obj[prop];
    },
    set: (obj, prop, value) => {
        console.log('set 操作')
        obj[prop] = value;
        return true
    }
});
proxy.push(1)  // 设置 get 操作*2 set 操作*2
proxy[0]  // 设置 get 操作


2VM287:4 设置 get 操作
2VM287:8 set 操作
VM287:4 设置 get 操作
1

除了get和set,Proxy还可以拦截其他共计13种操作

 

1.Object.defineProperty只能劫持对象的属性,而 Proxy 是直接代理对象。

由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而 Proxy 直接代理对象,不需要遍历操作,但Proxy只能监听第一层,稍后看vue3.0怎么解决这个问题。

2. Object.defineProperty对新增属性需要手动进行 Observe

由于 Object.defineProperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。

3. Proxy支持 13 种拦截操作,这是defineProperty所不具有的。

 

Proxy 是怎么实现深度代理的呢?

Reflect.get 返回的值是否为 object,若是则再通过 reactive 做代理,这样就达到了深度观测的目的了。 

 

依赖收集阶段

所谓的依赖在Vue3可简单理解为各种 effect 响应式函数,其中包括了属性依赖的 effect,计算属性 computedEffect 以及组件视图的 componentEffect

1、在视图挂载渲染时会执行一个 componentEffect,触发相关数据属性getter操作来完成视图依赖收集。

2、effect 函数执行也会触发相关属性的getter操作,此时操作了某个属性的 effect 也会被该属性对应进行收集

之所以说是响应式的,是因为effect方法回调中关联了被观测的数据属性,而effect一般是立即执行的,此时触发了该属性的 getter,进行依赖收集,当该属性触发 setter 时,便会触发执行收集的依赖。另外,这里每次effect执行时,当前的effect会被压入一个名为 activeReactiveEffectStack 的栈中,是在依赖收集的时候使用。

3.响应阶段

当触发属性 setter 时,通过 trigger 函数会执行属性对应收集的 effects,也包括 computedEffects,此时通过 scheduleRun 逐个调用 effect,最后完成视图更新。

 

上面我们讲过监测数组的时候可能触发多次 get/set, 那么如何防止触发多次的呢?

 

1、判断key是否为当前被代理对象target自身属性;

2、判断旧值与新值是否相等。

只有这两个条件必须满足一个,才有可能执行 trigger。

比如说:数组的push操作;同时改变了length和最后一个值;

由于判断旧值与新值是否相等  这个操作

 

hasChanged(value, oldValue)

const oldValue = target[key]; 
target[length]=4
value 也是4
 
这俩值是一样的,就可以过滤掉length的修改;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值