00后系列-00后从 0 到 1 手写 Vue3 响应式

lxf2023-12-21 14:10:02

从 0 到 1 手写 Vue3 响应式

前言

本文源自个人笔记,文笔一般,摘抄过多,重在分享,主打详细。

对比vue2

首先,Vue3中使用 Proxy对象 来代替 Vue 2 中基于 Object.defineProperty, 消除了 Vue 2 中基于 Object.defineProperty 所存在的一些局限,比如无法监听数组索引,length属性。默认监听动态添加属性和属性的删除操作,就很方便。

Reflect

ES6出现的新特性代码运行期间用来设置或获取对象成员(操作对象成员),

Reflect没有出现前使用Object的一些方法比如 Object.getPrototypeOf, Reflect也有对应的方法 Reflect.getPrototypeOf,两者都是一样的,不过Reflect更有语义。

Reflect.get(obj, prop) === obj[prop] Reflect.get(target, key, receiver) 中的receiver参数修改了this指向, 不加this指向target, 加了后指向receiver, 也就是说,谁调用它,this就指向谁。

const target = {
    name: '小浪',
    age: 22,
}

const handler = {
    get(target, key, receiver) {
        console.log(`获取对象属性${key}值`)
        // Reflect.get(target, key) == target[key], 不加 receiver
        // this指向 永远是 被代理的对象
        // 即使 Object.setPrototypeOf( 新对象 , 被代理的对象),this 依然指向 被代理的对象
        return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
        console.log(`设置对象属性${key}值`)
        return Reflect.set(target, key, value, receiver)
    },
    deleteProperty(target, key) {
        console.log(`删除对象属性${key}值`)
        return Reflect.deleteProperty(target, key)
    },
}

const proxy = new Proxy(target, handler)
console.log(proxy.age)
proxy.age = 21
console.log(delete proxy.age)

/**
output:
获取对象属性age值
22
设置对象属性age值
删除对象属性age值
true
*/

reactive本质

返回 一个 proxy对象,可实现深层次递归,也就是说,子元素存在引用类型会递归处理。

副作用函数与响应式数据

副作用函数,就是会产生副作用的函数,也就是副作用函数的执行会直接或者间接影响到其他函数,比如函数修改了一个全局变量,也算是副作用。

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

实现原理

  1. 先基于 监控对象属性更加完善 的 Proxy 和 可在代码运行期间操控对象成员的 Reflect,实现一个最基础版的响应式系统。
响应式系统-初级版
const isObject = val => val !== null && typeof val === 'object'
const hasOwn = (target,key) => Object.prototype.hasOwnProperty.call(target,key)
// 基础版 响应式代码
function reactive(target){
    if(!isObject(target)){
        return target;
    }
    const handler = {
        get(target,key,receiver){
            console.log('获取对象属性key',key);
            const result = Reflect.get(target,key,receiver);
            if(isObject(result)){
                return reactive(result);
            }else{
                return result;
            }
        },
        set(target,key,value,receiver){
            console.log('设置对象属性key',key);
            const old = Reflect.get(target,key,receiver)
            let flag = true;
            if(old !== value){
                flag = Reflect.set(target,key,value,receiver)
            }
            return flag; 
        },
        deleteProperty(target,key){
            console.log('删除对象属性key',key)
            return Reflect.deleteProperty(target, key)
        }

    }
    return new Proxy(target,handler);
}
let p = reactive({name:'1',age: 20})

p.age += 1;
console.log(p.name);
delete p.name;
  1. 一个响应式数据,也就是一个对象会有很多的属性key,每个 key 都有关联的一系列 effect 副作用函数,可用 集合 Set 存储。很多的key可以放Map里维护,这个 Map 是在对象存在的时候它就存在,对象销毁的时候它也要跟着销毁,而 js中 WeakMap 正好就有这样的特性,WeakMap 的 key 必须是一个对象,value 可以是任意数据,key 的对象销毁的时候,value 也会销毁。

结论: vue3 的响应式数据会用 WeakMap 来保存,key 为原对象,value为 n 个 属性key 和 n个 副作用函数集合Set 组成的 map。

  1. 基于以上理论,在 Proxy 代理 状态的 get 中 可 实现 自动收集 每个 key值 的 副作用依赖, set 中 可 循环触发 之前收集到的 副作用函数的执行。

    track函数:收集副作用依赖。trigger函数: 触发副作用函数的执行。

// targetMap 表里每个key都是一个普通对象 对应他们的 depsMap
let targetMap = new WeakMap()
let activeEffect
function track(target, key) {
    // 如果当前没有effect就不执行追踪
    if (!activeEffect) return
    // 获取当前对象的依赖图
    let depsMap = targetMap.get(target)
    // 不存在就新建
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }
    // 根据key 从 依赖图 里获取到到 effect 集合
    let dep = depsMap.get(key)
    // 不存在就新建
    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }
    // 如果当前effect不存在,才注册到 dep里
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
    }
}

// trigger 响应式触发
function trigger(target, key) {
    // 拿到 依赖图
    const depsMap = targetMap.get(target)
    if (!depsMap) {
        // 没有被追踪,直接 return
        return
    }
    // 拿到了 视图渲染effect 就可以进行排队更新 effect 了
    const dep = depsMap.get(key)

    // 遍历 dep 集合执行里面 effect 副作用方法
    if (dep) {
        dep.forEach(effect => {
            effect()
        })
    }
}
  1. 对于effect函数而言,除了执行放入的副作用函数本身,我们还需要考虑到 很多情况。
  • 添加新的依赖前要先清除(cleanup) 之前的依赖。

  • 值得注意的是,在 Proxy 代理 状态的set中 不能 直接遍历执行 收集到的副作用依赖,因为执行前会清除依赖,执行后又产生依赖,所以这会造成 副作用的依赖数组 的 无限增删过程。

方案: 就是根据 获取到的副作用集合 创建一个新的集合,只用于遍历执行。

  • 最后,我们需要支持effect函数的嵌套,因为vue组件本身是可以嵌套的,而且组件里会写 effect,那也就是 effect 嵌套了,所以必须支持嵌套。

方案: 利用栈的思想,执行 effect 函数前把当前 effectFn 入栈,执行完以后出栈,修改 activeEffect 为栈顶的 effectFn。

effect函数代码

const data = {
    a: 1,
    b: 2
}

let activeEffect
const effectStack = [];

function effect(fn) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      fn()
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];
  }
  effectFn.deps = []
  effectFn()
}

function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}
  1. 综上所述,即可完成一个比较完善的响应式系统。

响应式系统-中级版

// 判断是否为对象 ,注意 null 也是对象
const isObject = val => val !== null && typeof val === 'object'
// 判断key是否存在
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)

function reactive(target) {
   // 首先先判断是否为对象
   if (!isObject(target)) return target

   const handler = {
       get(target, key, receiver) {
           console.log(`获取对象属性${key}值`)
           // 这里还需要收集依赖,先空着
           track(target, key)

           const result = Reflect.get(target, key, receiver)
           // 递归判断的关键, 如果发现子元素存在引用类型,递归处理。
           if (isObject(result)) {
               return reactive(result)
           }
           return result
       },

       set(target, key, value, receiver) {
           console.log(`设置对象属性${key}值`)

           // 首先先获取旧值
           const oldValue = Reflect.get(target, key, reactive)

           // set 是需要返回 布尔值的
           let result = true
           // 判断新值和旧值是否一样来决定是否更新setter
           if (oldValue !== value) {
               result = Reflect.set(target, key, value, receiver)
               trigger(target, key)
           }
           return result
       },

       deleteProperty(target, key) {
           console.log(`删除对象属性${key}值`)

           // 先判断是否有key
           const hadKey = hasOwn(target, key)
           const result = Reflect.deleteProperty(target, key)

           if (hadKey && result) {
               // 删除时,是否需要 响应式触发trigger
               trigger(target, key)
           }

           return result
       },
   }
   return new Proxy(target, handler)
}

// activeEffect 表示当前正在走的 effect
let activeEffect = null
const effectStack = [];

function effect(fn) {
 const effectFn = () => {
     cleanup(effectFn)
     activeEffect = effectFn
     effectStack.push(effectFn);
     fn()
     effectStack.pop();
     activeEffect = effectStack[effectStack.length - 1];
 }
 effectFn.deps = []
 effectFn()
}

function cleanup(effectFn) {
   for (let i = 0; i < effectFn.deps.length; i++) {
       const deps = effectFn.deps[i]
       deps.delete(effectFn)
   }
   effectFn.deps.length = 0
}

// targetMap 表里每个key都是一个普通对象 对应他们的 depsMap
let targetMap = new WeakMap()

function track(target, key) {
   // 如果当前没有effect就不执行追踪
   if (!activeEffect) return
   // 获取当前对象的依赖图
   let depsMap = targetMap.get(target)
   // 不存在就新建
   if (!depsMap) {
       targetMap.set(target, (depsMap = new Map()))
   }
   // 根据key 从 依赖图 里获取到到 effect 集合
   let dep = depsMap.get(key)
   // 不存在就新建
   if (!dep) {
       depsMap.set(key, (dep = new Set()))
   }
   // 如果当前effectc 不存在,才注册到 dep里
   if (!dep.has(activeEffect)) {
       dep.add(activeEffect)
   }
}

// trigger 响应式触发
function trigger(target, key) {
   // 拿到 依赖图
   const depsMap = targetMap.get(target)
   if (!depsMap) {
       // 没有被追踪,直接 return
       return
   }
   // 拿到了 视图渲染effect 就可以进行排队更新 effect 了
   const dep = depsMap.get(key)
   // 遍历 dep 集合执行里面 effect 副作用方法
   // 避免 副作用的依赖数组 无限增删依赖问题
   const effectsToRun = new Set(dep);
   if (dep) {
       effectsToRun.forEach(effect => {
           effect()
       })
   }
}

let a = reactive({b : 1, c: 'name'})
effect(()=>{
   console.log('effect1',a.c);
   a.b = 2;
   delete a.c;
   effect(()=>{
       // delete a.b;
       console.log('effect2',a.b);
   })
})
computed 实现

计算属性 computed :本质就是 effect 函数的返回值。

当然,这需要我们先简单改造下之前写的effect函数,也就是把fn执行的结果返回出来。

function computed(fn) {
    const value = effect(fn);
    return value
}

const value = computed(() => {
    return obj.a + obj.b;
});

对比下 我们平时用的computed,我们会发现 初步实现的computed 会有以下问题。

  1. computed 这里的 effectFn 每次都是重新执行。
  2. 每次访问变量,执行了 所有的 effectFn 函数。
  3. 返回的结果,不是响应式数据。

为了解决问题1,可以给effect 加个options参数,让 effect 函数 有 返回函数 | 执行并返回函数 两种选项。

然后 computed 里创建一个对象,在 value 属性的 get 触发时,才重新执行effectFn函数,拿到最新的值。

function effect(fn,options) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      const res = fn()
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];
      return res;
  }
  effectFn.deps = []
  effectFn.options = []
  if(!options.lazy){
     effectFn()
  }
  // computed
  return effectFn
}
function computed(fn) {
    const effectFn = effect(fn,{
        lazy: true
    });
    const obj = {
        get value(){
            return effectFn()
        }
    }
    return obj
}

为了解决问题2,我们依旧是需要升级老代码,因为这个问题的本质是我们实现的trigger函数没有所谓的调度算法,

不能精准判断 哪些副作用是该重新执行的。当 trigger 响应式触发可以根据我们自定义的逻辑来调度后,可设置一个 dirty 变量 ,当 scheduler 被调用的时候就说明数据变了,这时候 dirty 设置为 true,然后取 value 的时候就重新计算,之后再改为 false,下次取 value 就直接拿计算好的值了。

至于问题3,其实很好解决,遵循之前的规则。

在访问属性时,track函数收集依赖,在修改属性值,trigger函数触发依赖的执行。

响应式系统-高级版

// 判断是否为对象 ,注意 null 也是对象
const isObject = val => val !== null && typeof val === 'object'
// 判断key是否存在
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)

function reactive(target) {
    // 首先先判断是否为对象
    if (!isObject(target)) return target

    const handler = {
        get(target, key, receiver) {
            console.log(`获取对象属性${key}值`)
            // 这里还需要收集依赖,先空着
            track(target, key)

            const result = Reflect.get(target, key, receiver)
            // 递归判断的关键, 如果发现子元素存在引用类型,递归处理。
            if (isObject(result)) {
                return reactive(result)
            }
            return result
        },

        set(target, key, value, receiver) {
            console.log(`设置对象属性${key}值`)

            // 首先先获取旧值
            const oldValue = Reflect.get(target, key, reactive)

            // set 是需要返回 布尔值的
            let result = true
            // 判断新值和旧值是否一样来决定是否更新setter
            if (oldValue !== value) {
                result = Reflect.set(target, key, value, receiver)
                trigger(target, key)
            }
            return result
        },

        deleteProperty(target, key) {
            console.log(`删除对象属性${key}值`)

            // 先判断是否有key
            const hadKey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)

            if (hadKey && result) {
                // 删除时,是否需要 响应式触发trigger
                trigger(target, key)
            }

            return result
        },
    }
    return new Proxy(target, handler)
}

// activeEffect 表示当前正在走的 effect
let activeEffect = {}
const effectStack = [];

function effect(fn,options = {}) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      const res = fn()
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];
      return res;
  }
  effectFn.deps = []
  effectFn.options = options;
  if(!options.lazy){
     effectFn()
  }
  // computed
  return effectFn
}

function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}

// targetMap 表里每个key都是一个普通对象 对应他们的 depsMap
let targetMap = new WeakMap()

function track(target, key) {
    if (!activeEffect) return
    // 获取当前对象的依赖图
    let depsMap = targetMap.get(target)
    // 不存在就新建
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }
    // 根据key 从 依赖图 里获取到到 effect 集合
    let dep = depsMap.get(key)
    // 不存在就新建
    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }
    // 如果当前effectc 不存在,才注册到 dep里
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
        activeEffect.deps.push(dep)
    }
}

// trigger 响应式触发
function trigger(target, key) {
    // 拿到 依赖图
    const depsMap = targetMap.get(target)
    if (!depsMap) {
        // 没有被追踪,直接 return
        return
    }
    // 拿到了 视图渲染effect 就可以进行排队更新 effect 了
    const dep = depsMap.get(key)
    // 遍历 dep 集合执行里面 effect 副作用方法
    // 避免 副作用的依赖数组 无限增删依赖问题
    const effectsToRun = new Set(dep);
    effectsToRun.forEach(effectFn => {
        if(effectFn.options.scheduler){
            effectFn.options.scheduler(effectFn)
        }else{
            effectFn()
        }
    })
}

function computed(fn) {
    let dirty = true;
    let val;
    const effectFn = effect(fn,{
        lazy: true,
        scheduler(fn){
            if(!dirty) {
                dirty = true
                trigger(obj, 'value');
            }
        }
    });
    const obj = {
        get value(){
            if(dirty){
                val = effectFn();
                dirty = false;
                // console.log('重新计算',val);
            }else{
                // console.log('旧值',val);
            }
            track(obj, 'value');
            return val;
        }
    }
    return obj
}

let obj = reactive({a : 1, b : 2})

effect(()=>{
    let res = computed(()=>{
        return obj.a + obj.b ;
    },{lazy: true})
    console.log('computed1',res.value);
    // console.log('computed2',res.value);
    obj.a = 3;
})
watch实现

watch,本质就是观测一个响应式数据,当数据发生变化时通知并执行相应的回调函数。

与 computed 类似, 通过effect设置scheduler选项,即可在数据发生变化时不是直接执行副作用函数,而是触发我们自定义的scheduler函数。

也就是说,在scheduler里 执行传入的回调函数,一个最简版watch实现也就写好了。

与vue3的watch相比,这个最简版的watch还缺了一些东西。

  1. vue3的watch,不仅支持响应式数据(基本数据类型 ,引用数据类型),还支持 getter函数 形式 。
  2. vue3的watch,可以在回调函数中拿到新值和旧值。
  3. vue3的watch,immediate选项为true时,立即执行一次。

针对问题1,其实很简单,只要封装一个通用的遍历读取属性的函数即可, 如果入参是函数,则 getter函数返回值 就是 读取函数的返回值。

// 遍历读取对象的每个值
function traverse(value,seen = new Set()){
    if(value !== 'object' || value === null || seen.has(value)){
        return;
    }
    seen.add(value);
    for(const k in value){
        traverse(value[k],seen);
    }
    return value;
}

function watch(source,cb){
    let getter;
    if(typeof source === 'function'){
        getter = source
    }else{
        getter = traverse(source)
    }
    effect(()=> getter(),{
        scheduler(){
            // 执行回调函数
            cb()
        }
    })
}

针对问题2,其实可以先回想下上文 介绍 computed 通过设置一个 dirty 变量 来 判断数据是否更新时的做法,

还是在effect的scheduler里处理,设置oldValue,newValue两个变量,重新执行effect函数的结果就是newValue,

执行回调函数后,曾经的新值newValue也就变成了旧值oldValue。

function watch(source,cb){
    let getter;
    let newValue,oldValue;
    if(typeof source === 'function'){
        getter = source;
    }else{
        getter = traverse(source);
    }
    const effectFn = effect(()=> getter(),{
        lazy: true,
        scheduler(){
            // 执行回调函数
            newValue = effectFn();
            cb(oldValue,newValue);
            oldValue = newValue;
        }
    })
    // 第一次调用时的值,也就是旧值
    oldValue = effectFn();
}
watch(()=>obj.a,(oldValue,newValue)=>{
    console.log('old',oldValue);
    console.log('new',newValue);
})
obj.a = 12;

针对问题3,其实就是一个调度时机的判定问题。

当 immediate选项为true时,新值 newValue 对应的 是 读取变量 执行的副作用函数结果,oldValue 在watch中还没赋过值,自然是undefined。

当 immediate选项为false时,我们想在变量第一次发生变化时获取旧值,就需要oldValue在watch函数创建之初就变为副作用函数结果。

至此,watch函数的主要功能已经完成,代码如下。

响应式系统-专业版

// 判断是否为对象 ,注意 null 也是对象
const isObject = val => val !== null && typeof val === 'object'
// 判断key是否存在
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)

function reactive(target) {
    // 首先先判断是否为对象
    if (!isObject(target)) return target

    const handler = {
        get(target, key, receiver) {
            console.log(`获取对象属性${key}值`)
            // 这里还需要收集依赖,先空着
            track(target, key)

            const result = Reflect.get(target, key, receiver)
            // 递归判断的关键, 如果发现子元素存在引用类型,递归处理。
            if (isObject(result)) {
                return reactive(result)
            }
            return result
        },

        set(target, key, value, receiver) {
            console.log(`设置对象属性${key}值`)

            // 首先先获取旧值
            const oldValue = Reflect.get(target, key, reactive)

            // set 是需要返回 布尔值的
            let result = true
            // 判断新值和旧值是否一样来决定是否更新setter
            if (oldValue !== value) {
                result = Reflect.set(target, key, value, receiver)
                trigger(target, key)
            }
            return result
        },

        deleteProperty(target, key) {
            console.log(`删除对象属性${key}值`)

            // 先判断是否有key
            const hadKey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)

            if (hadKey && result) {
                // 删除时,是否需要 响应式触发trigger
                trigger(target, key)
            }

            return result
        },
    }
    return new Proxy(target, handler)
}

// activeEffect 表示当前正在走的 effect
let activeEffect = {}
const effectStack = [];

function effect(fn,options = {}) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      const res = fn()
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];
      return res;
  }
  effectFn.deps = []
  effectFn.options = options;
  if(!options.lazy){
     effectFn()
  }
  // computed
  return effectFn
}

function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}

// targetMap 表里每个key都是一个普通对象 对应他们的 depsMap
let targetMap = new WeakMap()

function track(target, key) {
    if (!activeEffect) return
    // 获取当前对象的依赖图
    let depsMap = targetMap.get(target)
    // 不存在就新建
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }
    // 根据key 从 依赖图 里获取到到 effect 集合
    let dep = depsMap.get(key)
    // 不存在就新建
    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }
    // 如果当前effectc 不存在,才注册到 dep里
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
        activeEffect.deps.push(dep)
    }
}

// trigger 响应式触发
function trigger(target, key) {
    // 拿到 依赖图
    const depsMap = targetMap.get(target)
    if (!depsMap) {
        // 没有被追踪,直接 return
        return
    }
    // 拿到了 视图渲染effect 就可以进行排队更新 effect 了
    const dep = depsMap.get(key)
    // 遍历 dep 集合执行里面 effect 副作用方法
    // 避免 副作用的依赖数组 无限增删依赖问题
    const effectsToRun = new Set(dep);
    effectsToRun.forEach(effectFn => {
        if(effectFn.options.scheduler){
            effectFn.options.scheduler(effectFn)
        }else{
            effectFn()
        }
    })
}

function computed(fn) {
    let dirty = true;
    let val;
    const effectFn = effect(fn,{
        lazy: true,
        scheduler(fn){
            if(!dirty) {
                dirty = true
                trigger(obj, 'value');
            }
        }
    });
    const obj = {
        get value(){
            if(dirty){
                val = effectFn();
                dirty = false;
                // console.log('重新计算',val);
            }else{
                // console.log('旧值',val);
            }
            track(obj, 'value');
            return val;
        }
    }
    return obj
}

// 遍历读取对象的每个值
function traverse(value,seen = new Set()){
    if(value !== 'object' || value === null || seen.has(value)){
        return;
    }
    seen.add(value);
    for(const k in value){
        traverse(value[k],seen);
    }
    return value;
}

function watch(source,cb,options={}){
    let getter;
    let newValue,oldValue;
    if(typeof source === 'function'){
        getter = source;
    }else{
        getter = traverse(source);
    }
    const job = ()=>{
        newValue = effectFn();
        cb(oldValue,newValue);
        oldValue = newValue;
    }
    const effectFn = effect(()=> getter(),{
        lazy: true,
        scheduler: job
    })
    if(options.immediate){
        job()
    }else{
        oldValue = effectFn();
    }
}

简单总结

  1. 一个响应式数据的最基本实现依赖于对数据读取和设置操作的拦截,vue3是基于 监控对象属性更加完善 的 Proxy 和 可在代码运行期间操控对象成员的 Reflect来完成的,并且使用weakMap、Map、Set 将 源对象,属性key,每个属性key对应的一系列副作用函数建立起联系。
  2. 在 Proxy 代理 状态的 get 中 实现 自动收集 每个 key值 的 副作用依赖,封装成track函数。在 set 中循环触发之前收集到的副作用函数的执行,封装成trigger函数。
  3. 为了避免副作用函数进行不必要的更新,我们会在副作用函数执行前清除上一次建立起的响应联系,并注意到trigger函数直接遍历执行副作用函数集合会产生无限循环问题。
  4. 利用副作用函数栈结构,实现了副作用的嵌套,使得一个响应式数据只会收集读取其值的副作用,而不会相互影响。
  5. 为了实现计算属性computed ,effect函数加了lazy和scheduler两个选项,让响应系统具有调度性,而不是每次检测到依赖变动就会立即触发副作用函数,计算属性结合了这点,通过用一个变量标记,当计算属性依赖的响应式数据发生变化时,才会重新计算。
  6. watch属性,本质也是利用effect函数执行的可调度性,根据回调函数是否执行区分新值和旧值,通过自定义的scheduler函数可以灵活控制回调函数的执行时机。

主要参考文章

/post/713428…

/post/722246…

/post/711274…

…………

其实主要还是看《vue.js设计与实现》。

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!