Vue3响应式

1 基本实现原理

实现响应式的原理就是:拦截一个对象的读取和设置操作,读取时,将副作用函数存储起来,设置时,将副作用函数取出来执行。

副作用函数:会产生副作用的函数,会直接或间接影响其他函数的执行。
响应式数据:值发生变化后,副作用函数会自动重新执行数据。

当副作用函数effect执行时,会触发响应式数据的读取操作
当修改响应式数据的值时,会触发响应式数据的设置操作。

1.1 如何拦截对象的读取和设置操作

Vue2:在ES2015之前,只能通过Object.defineProperty()实现
Vue3:在ES2015+中,可以使用代理对象Proxy来实现

Proxy

Proxy可以创建一个代理对象,实现对其他对象的代理,也就是说Proxy只能代理对象,无法代理非对象,如字符串,数值等

代理:指的是对一个对象基本语义的代理,允许我们拦截并重新定义一个对象的基本操作。

基本操作:例如读取属性值、设置属性值等

1.2 数据结构设计

obj(代理target的代理对象).key1发生改变时,应该只重新执行effectFn1,不会导致effectFn2的重新执行
响应式数据与副作用函数的关系
WeapMap的键是原始对象target,值是一个Map实例
Map的键是原始对象targetkey,值是一个副作用函数组成的Set
WeaMap、Map和Set之间的关系
WeapMapkey时弱引用,不影响垃圾回收器的工作。一旦key被垃圾回收器收回,对应的键和值就访问不到了。

对一些关键步骤进行封装

  • 把读取属性时,在get拦截函数里收集副作用函数的逻辑封装到track函数中,意为追踪的含义
  • 把设置属性时,触发副作用函数重新执行的逻辑封装到trigger函数中
const obj = new Proxy(data, {
   
	get(target, key) {
   
		track(target, key)
		return target[key]
	},
	set(target, key, newVal) {
   
		target[key] = newVal
		trigger(target, key)
	}
})

1.3 分支切换产生的遗留副作用函数问题

const target = {
    ok: true, text: 'hello' }
const obj = new Proxy(target, {
    /* ... */})

effect(function effectFn() {
   
	document.body.innerText = obj.ok? obj.text : 'no no no'
})

分支切换是指当字段obj.ok的值发送变化时,代码执行的分支会跟着变化。

分支切换可能会产生遗留的副作用函数。如上面一段代码,obj.ok的初始值为true,会读取字段obj.text的值,所以此时副作用函数会与响应式数据建立如下联系:
在这里插入图片描述
ok的值修改为false,并触发副作用函数重新你执行后,obj.text的值不会再被读取,所以此时副作用函数effectFn不应该再被字段obj.text所对应的依赖集合收集。而遗留的副作用函数会导致不必要的更新。

解决方案:在每次副作用函数执行时,先把它从所有预支关联的依赖集合中删除,当副作用函数执行完毕后,再重新建立联系。此时重新建立的联系中就不会再包含遗留的副作用函数了。

实现方案:

  1. effectFn添加一个deps属性,该属性是一个数组,用来存储所有包含当前副作用函数的依赖集合。通过track函数进行收集。
// 在 get 拦截函数内调用 track 函数追踪变化
export const track = (target, key) => {
   
    if (!activeEffect || !shouldTrack) return
    let depsMap = bucket.get(target)
    if (!depsMap) {
   
        bucket.set(target, (depsMap = new Map()))
    }
    // 再根据key从depsMap中取得deps,里面存储着所有与当前key相关联的副作用函数effects
    let deps = depsMap.get(key)
    // 如果不存在,同样新建一个Set并与key关联
    if(!deps) {
   
        depsMap.set(key, (deps = new Set()))
    }
    // 把当前激活的副作用函数添加到依赖集合deps中
    deps.add(activeEffect)
    // deps就是一个与当前副作用函数存在联系的依赖集合
    activeEffect.deps.push(deps)
}
  1. 每次触发副作用函数时,对依赖进行清除
// 清除依赖
function cleanup(effectFn) {
   
    for (let i = 0; i < effectFn.deps.length; i++) {
   
        // deps是依赖集合
        const deps = effectFn.deps[i]
        // 将effectFn从依赖集合中移除
        deps.delete(effectFn)
    }
    // 重置effectFn.deps
    effectFn.deps.length = 0
}

// 注册副作用函数
export const effect = (fn) => {
   
    const effectFn = () => {
   
        // 调用cleanup函数完成清除工作
        cleanup(effectFn)
        // 当调用effect注册副作用函数时,将副作用函数fn赋值给activeEffect
        activeEffect = effectFn
        // 在调用副作用函数之前将当前副作用函数压入栈中
        effectStack.push(effectFn)
        const res = fn()
        // 执行完毕后,将当前副作用函数弹出栈,并还原为之前的值
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
        return res
    }
    // activeEffect.deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = []
    effectFn()
    // 将副作用函数作为返回值返回
    return effectFn
}

1.4 无限递归循环

const target = {
    foo: 1 }
const obj = new Proxy(target, {
    /* ... */})

effect(() => {
   
	obj.foo++; // obj.foo = obj.foo + 1
})

自增的语句既会引发读取操作,也会引发设置操作。

解决方案:在trigger动作发生时增加守卫条件:如果trigger触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行

// 在 set 拦截函数内调用 trigger 函数触发变化
export const trigger = (target, key, type, newVal) => {
   
    // 根据target从桶中取得depsMap
    const depsMap = bucket.get(target)
    if(!depsMap) return
    // 根据key取得所有副作用函数effects
    const effects = depsMap.get(key)

    // 把副作用函数从桶里取出来执行
    const effectsToRun = new Set()
    // 将与 key 相关联的副作用函数添加到 effectsToRun
    effects && effects.forEach(effectFn => {
   
        // 如果触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
        if (effectFn !== activeEffect) {
   
           
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vue 3 的响应式原理可以通过 Proxy 对象和 Reflect API 来实现。下面是对 Vue 3 响应式原理的简要解释: 1. Proxy 对象:Vue 3 使用 Proxy 对象来实现对数据的劫持。当访问被代理的对象时,Proxy 可以捕获到对对象的访问,并触发相应的操作。 2. Reactive 函数:Vue 3 提供了一个叫做 reactive 的函数,用于将一个普通的 JavaScript 对象转换为响应式对象。通过 reactive 函数,Vue 3 在内部使用 Proxy 对象对数据进行代理。 3. 响应式更新:当响应式对象的属性被访问或修改时,Proxy 对象会触发相应的 get 和 set 操作。在这些操作中,Vue 3 使用依赖追踪的机制来追踪属性与组件之间的关联关系,并进行更新。 4. Track 和 Trigger:Vue 3 使用 track 和 trigger 函数来进行依赖追踪和触发更新。track 函数用于追踪属性与组件之间的关联关系,而 trigger 函数则用于触发相关联组件的更新。 5. Computed 和 Watch:Vue 3 的响应式系统还支持计算属性和侦听器。通过使用 computed 函数,可以创建计算属性,它们会根据其依赖的响应式属性进行自动更新。而 watch 函数可以用来监听响应式属性的变化,并执行相应的回调函数。 综上所述,Vue 3 的响应式原理通过 Proxy 对象和 Reflect API 来实现属性的劫持和触发更新,利用依赖追踪机制来建立属性与组件之间的关联关系,通过 track 和 trigger 函数实现属性的追踪和更新,同时支持计算属性和侦听器。这样,当响应式数据发生变化时,相关联的组件会自动更新。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值