首页IT科技vue3的响应式(【手撕源码】vue3响应式原理解析(文末抽奖))

vue3的响应式(【手撕源码】vue3响应式原理解析(文末抽奖))

时间2025-06-20 20:35:38分类IT科技浏览6549
导读:🐱 个人主页:不叫猫先生...

🐱 个人主页:不叫猫先生

🙋‍♂️ 作者简介:2022年度博客之星前端领域TOP 2              ,前端领域优质作者              、阿里云专家博主                    ,专注于前端各领域技术       ,共同学习共同进步       ,一起加油呀!

💫优质专栏:vue3从入门到精通                    、TypeScript从入门到实践

📢 资料领取:前端进阶资料以及文中源码可以找我免费领取

🔥 前端学习交流:博主建立了一个前端交流群                    ,汇集了各路大神              ,一起交流学习       ,期待你的加入!(文末有我wx或者私信)              。

一       、认识Proxy

Proxy 用于修改某些操作的默认行为                    ,等同于在语言层面做出修改              ,所以属于一种“元编程             ”(meta programming),即对编程语言进行编程                    。

Proxy 可以理解成                    ,在目标对象之前架设一层“拦截                     ”                    ,外界对该对象的访问,都必须先通过这层拦截              ,因此提供了一种机制                    ,可以对外界的访问进行过滤和改写       。

new Proxy(target,handler)表示生成一个Proxy实例       ,target参数表示所要拦截的目标对象              ,它可以是任意类型的对象                    ,包括内置的数组       ,函数等       ,handler也是一个对象                    ,用来定制拦截行为              ,当发生某些操作时触发该对象       。

二       、原理分析

1.reactive

声明副作用变量       ,如果该变量没有值就不进行追踪                    。在 Vue2 的时候                    ,有一个“全局变量       ”              ,叫做 Dep.target – watcher,vue3中还要有这么一个全局变量                    ,就是activeEffect              。

//副作用变量 let activeEffect;

targetMap用来存放依赖

let targetMap = new WeakMap();

判断传入的数据data是否为对象                    ,需要除去data为null的情况       。因为typeof null === ‘object’,null的机器码都是0              ,object机器码为000

function isObject(data) { return data && typeof data === object }

声明reactive函数                    ,返回proxy实例                    。proxy支持get                    、set              、deleteProperty       、has                    、ownKeys等方法              。

get 通过Reflect.get(target, key, receiver)获取到属性为key的值 track收集依赖 ret为对象则递归为对象创建proxy代理 set Reflect.set(target, key, value, receiver)设置属性为key的值 trigger 执行更新

receiver代表当前proxy对象或者继承proxy的对象       ,保证传递正确的this 给 getter              、setter。

export function reactive(data) { //判断是否为对象 if (!isObject(data)) return // 返回proxy实例 return new Proxy(data, { get(target, key, receiver) { const ret = Reflect.get(target, key, receiver); // 收集依赖 track(target, key) //如果获取的数据还是对象的话就递归              ,继续为此对象创建 Proxy 代理 return isObject(ret) ? reactive(ret) : ret }, //set修改数据                    ,需要返回一个布尔值 set(target, key, value,receiver) { // 首先获取旧值 const oldValue = Reflect.get(target, key, receiver) // 判断新值和旧值是否一样来决定是否更新setter let result = true; // 当新值不等于旧值的时候执行更新草错 if (oldValue !== value) { result = Reflect.set(target, key, value, receiver) // 更新操作 trigger(target, key) } //返回布尔类型 return result }, deleteProperty(target, key) { //首先需要判断是否有要删除的key const hasK = hasKey(target, key) const ret = Reflect.deleteProperty(target, key) //存在key且有值则更新 if(hasK&&ret){ //更新 trigger(target, key) } return ret }, has(target, key) { track(target, key) const ret = Reflect.has(target, key) }, ownKeys(target, key) { track(target) return Reflect.ownKeys(targety) }, }) } // 判断对象中key是否存在 const hasKey = (target, key) => Object.prototype.hasOwnProperty.call(target, key)

2.track

track里面会收集各种依赖       ,把依赖关系做成各种映射的关系       ,映射关系就叫 targetMap                    , 内部拿到这个key              ,就可以通过映射关系找到对应的value       ,就可以影响这个执行函数                    ,

function track(target, key) { // 如果当前没有effect就不执行追踪 if (!activeEffect) return //找target有么有被追踪 let depsMap = targetMap.get(target); //判断target是否为空              ,如果target为空则没被追踪,则set设置一个值 if (!depsMap) targetMap.set(target, (depsMap = new Map())); //判断depsMap中有没有key                    ,没有key就set一个(判断target.key有没有被追踪) let dep = depsMap.get(key) // 如果key没有被追踪                    ,那么添加一个 if (!dep) depsMap.set(key, (dep = new Set())) //触发副作用函数 trackEffect(dep) } //副作用函数 function trackEffect(dep) { //相当于 Dep.target && dep.add(Dep.target) //如果key没有添加activeEffect,则添加一个 if (!dep.has(activeEffect)) dep.add(activeEffect); }

3.trigger

修改数据时通过 trigger目标对象找到key              ,根据映射关系找到cb函数执行更新视图                    。

function trigger(target, key) { // 获取依赖数据                    ,对依赖数据循环       , const depsMap = targetMap.get(target) console.log(depsMap,depsMap) if (!depsMap) return //如果effect存在则执行run方法              ,run方法就是执行的视图更新回调 depsMap.get(key).forEach(effect => effect && effect.run() ); }

4.ref

ref中声明了一个RefImpl类                    ,初始化时传入参数init                    。

get

追踪变量       ,收集依赖 返回初始化变量的值

set

修改旧值       ,赋新值 trigger 更新 export function ref(init) { class RefImpl { constructor(init) { // 接收传过来的参数 this.__value = init; } //获取数据                    ,直接返回传过来的数据 get value() { track(this, value) return this.__value } //更新数据 set value(newVal) { this.__value = newVal; trigger(this, value) } } return new RefImpl(init); }

5.effect

effect第一个参数是函数              ,如果这个函数中有使用 ref/reactive 对象       ,当该对象的值改变的时候effect就会执行。

function effect(fn,option={}){ //effect 数据类型为ReactiveEffect                    ,一上来就会执行run方法              ,之后可以自定义执行run方法,即设置option的内容 let __effect = new ReactiveEffect(fn); if(!option.lazy){ __effect.run(); } return __effect }

6.ReactiveEffect

声明ReactiveEffect类                    ,间接定义effect的数据类型              。

class ReactiveEffect{ constructor(fn){ this.fn = fn; } // 依赖收集之前触发 run(){ activeEffect = this; return this.fn() } }

7.computed

计算属性

export function computed(fn){ //只考虑函数情况 let __computed; const e = effect(fn,{lazy:true }) __computed = { get value(){ return e.run(); } } return __computed }

8.mount

mount的参数instance相当于整个app                    ,el相当于挂在的节点

export function mount(instance,el){ // 执行effect effect(function(){ instance.$data && update(instance,el) }) instance.$data = instance.setup(); //更新节点 update(instance,el) function update(instance,el){ //挂载节点的render函数返回值内容复制给节点的innerHTML,进行更新 el.innerHTML = instance.render(); } }

三、源码地址

附:源码地址

🌟粉丝福利(抽奖)

《低代码开发实战——基于低代码平台构建企业级应用》

抽奖规则:抽奖助手小程序随机抽奖

活动时间:即日起至2023年4月18日 12:00

温馨提示:参与活动者提前参加博主wx(zbsguilai)

              ,以避免错过中奖通知                    。

声明:本站所有文章                    ,如无特殊说明或标注       ,均为本站原创发布       。任何个人或组织              ,在未征得本站同意时                    ,禁止复制                    、盗用                    、采集、发布本站内容到任何网站              、书籍等各类媒体平台              。如若本站内容侵犯了原著者的合法权益       ,可联系我们进行处理                    。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
rtsp浏览器播放(webrtc streamer&前端页面js播放摄像头rtsp流)