Vue响应式原理推导过程

Vue响应式原理推导过程

1. 最基础的响应式模型

最初的响应式模型非常简单,就是手动实现函数与对象的绑定关系:

javascript 复制代码
/**
 * 阶段一:函数与对象的绑定
 *
 * 解释:一个或多个函数有该对象的属性,在对象属性发生变化时,重新调用函数,结果也发生变化
 *
 *
 * 缺点:
 *      1.函数需要在对象属性修改后重新手动调用
 *      2.对象属性依赖绑定,某一个属性发生变化,所有函数都要调用
 *      3.多对象依赖绑定,某一对象发生变化,所有对象依赖函数都要调用
 *      4.函数需要一个一个手动调用
 */

const obj={
    name:'mike',
    age:18
}

function foo(){
    console.log(obj.name)
    console.log(obj.age)
}

//初始调用
foo() //mike 18

//修改对象属性
obj.name='jack'

//重新调用
foo() //jack 18

存在的问题:

  • 需要手动调用函数
  • 对象属性发生变化时,所有函数都要手动重新调用
  • 多对象依赖混乱
  • 每个函数需要单独调用

优势:

  • 概念简单,容易理解
  • 实现方式直接明了
  • 不需要额外的监听机制,代码量少

2. 集中管理响应函数

改进方向是使用数组收集需要响应的函数,方便统一管理和调用:

javascript 复制代码
/**
 * 阶段二:函数与对象的绑定
 *
 * 改变:1.新增收集函数
 *
 * 解释:一个或多个函数有该对象的属性,在对象属性发生变化时,重新调用函数,结果也发生变化
 *
 *
 * 缺点:
 *      1.函数需要在对象属性修改后重新手动调用
 *      2.对象属性依赖绑定,某一个属性发生变化,所有函数都要调用
 *      3.多对象依赖绑定,某一对象发生变化,所有对象依赖函数都要调用
 *
 * 优点:
 *      1.函数可以一起调用
 */

const obj={
    name:'mike',
    age:18
}


//需要响应式的函数都存入该数组里面
const reactiveFns=[]

//收集响应式函数
function watchFn(fn){
    reactiveFns.push(fn)
}

function foo(){
    console.log('first:'+obj.name)
    console.log('first:'+obj.age)
}

//调用收集函数(第一种调用方法,foo仍然可以手动调用)
watchFn(foo)

//调用收集函数(第二种调用方法,需使用数组才可手动调用)
watchFn(function(){
    console.log('second:'+obj.name)
    console.log('second:'+obj.age)
})

reactiveFns.forEach(fn=>{
    fn()
})

// first:mike
// first:18
// second:mike
// second:18


obj.name='jack'

reactiveFns.forEach(fn=>{
    fn()
})

// first:jack
// first18
// second:jack
// second:18

改进点:

  • 函数可以统一调用
  • 响应式函数集中管理

仍存在的问题:

  • 属性修改后需要手动触发函数调用
  • 不同对象的依赖函数混在一起

优势:

  • 统一管理所有需要响应的函数
  • 批量执行响应函数,减少重复代码
  • 可以动态添加新的响应函数

3. 使用Depend类管理依赖

为了更好地组织代码,引入Depend类来管理依赖关系:

javascript 复制代码
/**
 * 阶段三:函数与对象的绑定
 *
 * 改变:
 *      1.新增收集函数
 *      2.新增类Depend
 *
 * 解释:一个或多个函数有该对象的属性,在对象属性发生变化时,重新调用函数,结果也发生变化
 *
 *
 * 缺点:
 *      1.函数需要在对象属性修改后重新手动调用
 *      2.对象属性依赖绑定,某一个属性发生变化,所有函数都要调用
 *
 *
 * 优点:
 *      1.函数可以一起调用
 *      2.对象可以分类管理
 */

class Depend{
    constructor(){
        this.reactiveFns=[]
    }

    //收集依赖函数
    addDepend(fn){
        this.reactiveFns.push(fn)
    }

    //调用依赖函数
    notify(){
        this.reactiveFns.forEach(fn=>{
            fn()
        })
    }
}

// ========= 分类管理 obj =============

const obj={
    name:'mike',
    age:18
}

const dep=new Depend()

dep.addDepend(function(){
    console.log('second:'+obj.name)
    console.log('second:'+obj.age)
})

dep.notify()
// second:mike
// second:18

obj.name='jack'

dep.notify()
// second:jack
// second:18

改进点:

  • 对象依赖可以分类管理
  • 代码结构更加清晰

优势:

  • 面向对象的设计,更加结构化
  • 可以为不同对象创建独立的依赖管理
  • 封装了依赖收集和通知逻辑,使用更加灵活
  • 为后续扩展提供了基础设施

4. 使用Object.defineProperty实现自动响应

通过Object.defineProperty监听对象的属性变化,实现自动响应:

javascript 复制代码
/**
 * 阶段四:函数与对象的绑定
 *
 * 改变:
 *      1.新增收集函数
 *      2.新增类Depend
 *      3.使用Object.defineProperty监听对象属性====>vue2
 *
 * 解释:一个或多个函数有该对象的属性,在对象属性发生变化时,重新调用函数,结果也发生变化
 *
 *
 * 缺点:
 *      1.对象属性依赖绑定,某一个属性发生变化,所有函数都要调用
 *
 *
 * 优点:
 *      1.函数可以一起调用
 *      2.对象可以分类管理
 *      3.属性被监听,属性修改时,函数自动调用
 */

class Depend{
    constructor(){
        this.reactiveFns=[]
    }

    //收集依赖函数
    addDepend(fn){
        this.reactiveFns.push(fn)
    }

    //调用依赖函数
    notify(){
        this.reactiveFns.forEach(fn=>{
            fn()
        })
    }
}

// ========= 分类管理 obj =============

const obj={
    name:'mike',
    age:18
}

const dep=new Depend()

dep.addDepend(function(){
    console.log('second:'+obj.name)
    console.log('second:'+obj.age)
})

Object.keys(obj).forEach(key=>{
    let value=obj[key]

    Object.defineProperty(obj,key,{
        set:(newValue)=>{
            value=newValue
            dep.notify()
        },
        get:()=>{
            return value
        }
    })
})

dep.notify()
// second:mike
// second:18

obj.name='jack'//监听修改后调用dep.notify()
// second:jack
// second:18

改进点:

  • 属性被监听,修改时自动调用依赖函数
  • 无需手动调用notify()

优势:

  • 实现了真正的响应式,属性变化自动触发更新
  • 简化了使用流程,无需手动监听属性变化
  • 提供了更加透明的使用体验
  • 在底层实现变化监听,应用代码更加纯净

5. 完善依赖收集与精确响应

为每个对象的每个属性创建独立的依赖收集器,实现更精确的响应:

javascript 复制代码
/**
 * 阶段五:函数与对象的绑定
 *
 * 改变:
 *      1.新增收集函数
 *      2.新增类Depend
 *      3.使用Object.defineProperty监听对象属性====>vue2
 *      4.新增map结构封装函数,给每个对象和每个属性设置对应的dep实例
 *
 * 解释:一个或多个函数有该对象的属性,在对象属性发生变化时,重新调用函数,结果也发生变化
 *
 *
 *
 *
 * 优点:
 *      1.函数可以一起调用
 *      2.对象可以分类管理
 *      3.属性被监听,属性修改时,函数自动调用
 *      4.对象属性之间减少依赖关系,独立调用对应函数
 */

class Depend{
    constructor(){
        this.reactiveFns=new Set() //避免重复添加依赖函数
    }

    //收集依赖函数
    addDepend(fn){
        this.reactiveFns.add(fn)
    }

    //调用依赖函数
    notify(){
        this.reactiveFns.forEach(fn=>{
            fn()
        })
    }
}

//存储响应式函数
let reactiveFn=null

function watchFn(fn){
    reactiveFn=fn
    fn() //激活监听响应
}

//对象map源头,弱引用weakMap
const objMap=new WeakMap()

//map结构的封装函数(分配dep实例)
function getDepend(obj,key){
    //1.根据源头map,找到obj对应的map
    let map=objMap.get(obj)
    //若map不存在
    if(!map){
        map=new Map()
        objMap.set(obj,map)
    }

    //2.根据obj的map对象,找到key对应的depend对象(有一个选择,如果key也是一个对象,是否需要深层响应)
    let dep=map.get(key)
    //若dep不存在
    if(!dep){
        dep=new Depend()
        map.set(key,dep)
    }

    return dep
}


//添加响应式函数
function reactive(obj){
    Object.keys(obj).forEach(key=>{
    let value=obj[key]

    Object.defineProperty(obj,key,{
        set:(newValue)=>{
            value=newValue
            const dep=getDepend(obj,key)
            dep.notify()
        },
        get:()=>{
            const dep=getDepend(obj,key)
            //防止重复添加依赖函数
            dep.addDepend(reactiveFn)
            return value
        }
    })
})
    return obj
}

改进点:

  • 对象属性与依赖函数的关系更加精确
  • 只触发依赖特定属性的函数
  • 自动收集依赖关系

优势:

  • 细粒度的依赖收集,只有真正依赖某属性的函数才会被触发
  • 使用WeakMap避免内存泄漏问题
  • 使用Set避免重复添加依赖函数
  • 自动在属性获取时收集依赖,无需手动指定依赖关系
  • 提供了完整的reactive函数,封装了响应式逻辑

6. 使用Proxy实现全面的响应式

最终使用ES6的Proxy替代Object.defineProperty,实现更强大的响应式系统:

javascript 复制代码
/**
 * 阶段六:函数与对象的绑定
 *
 * 改变:
 *      1.新增收集函数
 *      2.新增类Depend
 *      3.使用Proxy代理对象属性====>vue3
 *      4.新增map结构封装函数,给每个对象和每个属性设置对应的dep实例
 *
 * 解释:一个或多个函数有该对象的属性,在对象属性发生变化时,重新调用函数,结果也发生变化
 *
 *
 *
 *
 * 优点:
 *      1.函数可以一起调用
 *      2.对象可以分类管理
 *      3.属性被监听,属性修改时,函数自动调用
 *      4.对象属性之间减少依赖关系,独立调用对应函数
 */

//添加响应式函数
function reactive(obj){
    const objProxy=new Proxy(obj,{
        set(target,key,newValue,receiver){
            Reflect.set(target,key,newValue,receiver)
            const dep=getDepend(target,key)
            dep.notify()
        },
        get(target,key,receiver){
            const dep=getDepend(target,key)
            dep.addDepend(reactiveFn)
            return Reflect.get(target,key,receiver)
        }
    })
    return objProxy
}

Proxy的优势:

  1. 可以监听动态添加的属性
  2. 可以监听数组的变化
  3. 可以监听更多种类的操作(不仅限于get/set)
  4. 性能更好,不需要递归遍历对象的所有属性
  5. 返回的是一个新对象,不会修改原始对象
  6. 可以拦截更多的操作,如删除属性、检查属性是否存在等

总结

JavaScript响应式原理的演进经历了以下几个阶段:

  1. 最基础的手动绑定与调用
  2. 集中管理响应函数
  3. 使用Depend类组织依赖关系
  4. Object.defineProperty实现自动响应
  5. 完善依赖收集与精确响应
  6. 使用Proxy实现全面的响应式系统
相关推荐
前端小巷子14 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
尖椒土豆sss26 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
画月的亮34 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf
知识分享小能手6 小时前
Vue3 学习教程,从入门到精通,Axios 在 Vue 3 中的使用指南(37)
前端·javascript·vue.js·学习·typescript·vue·vue3
伍哥的传说7 小时前
Mitt 事件发射器完全指南:200字节的轻量级解决方案
vue.js·react.js·vue3·mitt·组件通信·事件管理·事件发射器
一枚小小程序员哈10 小时前
基于Vue + Node能源采购系统的设计与实现/基于express的能源管理系统#node.js
vue.js·node.js·express
一枚小小程序员哈14 小时前
基于Vue的个人博客网站的设计与实现/基于node.js的博客系统的设计与实现#express框架、vscode
vue.js·node.js·express
定栓14 小时前
vue3入门-v-model、ref和reactive讲解
前端·javascript·vue.js
LIUENG15 小时前
Vue3 响应式原理
前端·vue.js
wycode16 小时前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js