hello,大家好。我是你们的ys指风不买醉。
今天我们来看看reactive
在vue中怎么实现响应式的。什么是响应式?在Vue.js中,响应式指 vue实例数据变化触发相关联dom更新的特性,这样我们程序员就不用手动操作dom,这样方便代码维护。
Vue 响应式视图可以通过es5 object.defineProperty 或者 es6 proxy 来实现,会劫持数据变化,追踪数据变化。当数据变化后,以依赖收集的发布-订阅模式,触发相关联数据更新。想知道es5 object.defineProperty 或者 es6 proxy区别 ,文末给出了答案哦。
首先,可以这样回答 ref
和 reactive
的区别:
reactive
是基于 ES6 中的Proxy
代理实现的响应式系统。它通过代理一个引用类型对象(如对象或数组),对对象的属性进行拦截。当属性值被读取时,会触发依赖收集,将当前的effect
(副作用函数)与该属性关联;当属性值被修改时,会触发更新,通知所有关联的effect
重新执行。ref
则是一个更通用的响应式封装方式,既可以用于原始数据类型(如数字、字符串等),也可以用于引用数据类型(如对象、数组等)。当代理原始数据类型时,ref
会返回一个RefImpl
实例,通过value
属性来读取和修改值,并利用get
和set
方法实现响应式更新。当代理引用数据类型时,ref
会将其内部数据通过reactive
进行代理,从而复用reactive
的响应式机制。
二、手撕源码:实现mini版reactive
2.1 代理工厂函数(含详细注释)
js
// reactive.js
// WeakMap优势:1.键必须是对象 2.弱引用(不影响垃圾回收)
import {
mutableHandlers,
} from './baseHandlers'
const reactiveMap = new WeakMap(); // 存储已代理对象的缓存池
export function reactive(target) {
// 创建代理对象的标准化流程
return createReactiveObject(
target,
reactiveMap,
mutableHandlers // 基础拦截器
);
}
function createReactiveObject(target, proxyMap, handlers) {
// 类型安全检查:只代理对象类型
if (typeof target !== 'object' || target === null) {
console.warn(`无法代理非对象类型:${target}`);
return target;
}
// 检查是否已有缓存(避免重复代理)
const existingProxy = proxyMap.get(target);
if (existingProxy) {
console.log('检测到重复代理,直接返回缓存');
return existingProxy;
}
// 创建代理核心操作
const proxy = new Proxy(target, handlers);
// 缓存代理结果
proxyMap.set(target, proxy);
console.log('新建代理对象并缓存', proxy);
return proxy;
}
可以增加设置浅层代理shallowReactive
js
import {
mutableHandlers,
shallowHandlers
} from './baseHandlers'
export const reactiveMap = new WeakMap(); // 全局依赖地图
export const shallowReactiveMap = new WeakMap(); // 浅依赖
// target: 代理的目标对象
export const reactive = (target) => {
// 返回代理对象
return createReactiveObject(target,reactiveMap,mutableHandlers)
}
export const shallowReactive = (target) => {
return createReactiveObject(target,shallowReactiveMap,shallowHandlers)
}
// proxyMap 代理地图 proxyHandlers 代理处理函数
function createReactiveObject(target,proxyMap,proxyHandlers) {
if (typeof target !== 'object') {
console.warn('target must be an object');
return target
}
const existingProxy = proxyMap.get(target);
if (existingProxy) {
// 该对象是否已经被代理过(已经是响应式对象)
return existingProxy
}
// 执行代理操作(将target处理成响应式)
const proxy = new Proxy(target,proxyHandlers)
// 往 proxyMap 增加 proxy, 把已经代理过的对象缓存起来
proxyMap.set(target,proxy)
return proxy
}
2.2 代理拦截器实现(逐步解析)
js
// baseHandlers.js
import { track, trigger } from "./effect";
// 创建可配置的getter生成器
function createGetter(shallow = false) {
return function get(target, key, receiver) {
console.log(`拦截【读取】操作:${String(key)}`);
// 反射API保证this正确性
const res = Reflect.get(target, key, receiver);
// 依赖收集(记录当前属性被哪些effect使用)
track(target, "get", key);
// 递归代理:当值为对象时继续代理
if (isObject(res) && !shallow) {
return reactive(res);
}
return res;
};
}
// 创建setter生成器
function createSetter() {
return function set(target, key, value, receiver) {
console.log(`拦截【写入】操作:${String(key)}=${value}`);
const oldValue = target[key];
const success = Reflect.set(target, key, value, receiver);
// 只有值变化时才触发更新(性能优化)
if (success && oldValue !== value) {
trigger(target, "set", key);
}
return success;
};
}
// 导出标准处理器
export const mutableHandlers = {
get: createGetter(),
set: createSetter()
};
2.3 为什么需要Reflect?
使用Reflect API的三个关键原因:
- 保证receiver正确性:正确处理继承场景中的this指向
- 操作标准化 :统一对象操作方式(如
in
操作符对应Reflect.has
) - 返回值规范化:布尔值表示操作是否成功
三、依赖追踪系统:Vue的"记忆大师"
3.1 核心数据结构图解
js
graph TD // 看不明白的宝子,可以先往下看哦
A[WeakMap] --> B(原始对象)
B --> C[Map]
C --> D(属性key)
D --> E[Set]
E --> F(effect函数1)
E --> G(effect函数2)
3.2 effect实现(带详细流程注释)
js
// effect.js
let activeEffect = null; // 当前正在收集的副作用函数
const targetMap = new WeakMap(); // 全局依赖存储中心
export function effect(fn, options = {}) {
console.log('注册副作用函数:', fn.name || '匿名函数');
const effectFn = () => {
try {
// 设置为当前活跃effect
activeEffect = effectFn;
// 执行函数触发依赖收集
return fn();
} finally {
// 执行完成后重置
activeEffect = null;
}
};
// 立即执行一次(除非配置lazy)
if (!options.lazy) {
effectFn();
}
return effectFn;
}
3.3 依赖收集与触发(逐步解析)
js
// track函数:记录依赖关系
export function track(target, type, key) {
if (!activeEffect) return;
console.log(`收集依赖:${target}的${String(key)}属性`);
// 三级依赖结构查询
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
// 避免重复添加
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
console.log(`新增依赖:当前共有${dep.size}个依赖`);
}
}
// trigger函数:触发更新
export function trigger(target, type, key) {
console.log(`触发更新:${target}的${String(key)}属性变更`);
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
// 创建副本避免无限循环
const effects = new Set(dep);
effects.forEach(effect => {
console.log('执行副作用函数:', effect.name || '匿名函数');
effect();
});
}
}
四、实战测试:让我们的系统跑起来
html
<!DOCTYPE html>
<body>
<div id="app"></div>
<script type="module">
import { reactive, effect } from './reactivity.js';
const state = reactive({
counter: 0,
user: {
name: '小明'
}
});
// 注册副作用
effect(() => {
document.getElementById('app').innerHTML = `
<h1>${state.user.name}的计数器:${state.counter}</h1>
`;
});
// 测试响应式
setInterval(() => {
state.counter++;
if(state.counter % 5 === 0) {
state.user.name = state.user.name === '小明' ? '小红' : '小明';
}
}, 1000);
</script>
</body>
</html>
运行结果:
- 每秒计数器自动递增
- 每5秒切换用户名
- 界面自动更新,无需手动操作DOM
五、常见面试问题深度解析
5.1 为什么Proxy比defineProperty更好?
对比维度 | defineProperty | Proxy |
---|---|---|
数组处理 | 需要重写数组方法 | 直接检测索引变化 |
新增属性 | 需要Vue.set | 自动检测 |
删除属性 | 需要Vue.delete | 自动检测 |
性能 | 初始化时递归转换 | 按需代理 |
嵌套对象 | 一次性深度转换 | 惰性代理 |
5.2 再从源码回调:reactive与ref的区别是什么?
js
// ref实现原理(简化版)
class RefImpl {
constructor(value) {
this._value = isObject(value) ? reactive(value) : value;
}
get value() {
track(this, 'value');
return this._value;
}
set value(newVal) {
this._value = isObject(newVal) ? reactive(newVal) : newVal;
trigger(this, 'value');
}
}
function ref(value) {
return new RefImpl(value);
}
核心区别:
- 包装方式:ref通过对象包装,reactive直接代理
- 访问方式 :ref需要
.value
,reactive直接访问 - 类型支持:ref支持所有类型,reactive仅对象
六、从源码看框架设计
6.1 Vue响应式系统的精妙设计
- 分层架构:代理层/依赖层/调度层分离(多个文件分块)
- 惰性代理:嵌套对象按需代理,提升性能(shallowReactive 浅层代理)
- 弱引用存储:避免内存泄漏(weakMap)
- 批处理更新:通过微任务队列合并更新(Promise处理)
6.2 性能优化技巧
js
// 调度器示例(源码简化版)
function trigger(target, key) {
const effects = getDeps(target, key);
// 批处理更新
Promise.resolve().then(() => {
effects.forEach(effect => {
if (effect.scheduler) {
effect.scheduler();
} else {
effect();
}
});
});
}
优化手段:
- 异步更新队列
- 副作用去重
- 调度优先级控制
七、总结
通过实现这个mini响应式系统,我们掌握了:
- Proxy的拦截原理
- 依赖收集的发布-订阅模式
- 响应式系统的分层设计
其实用js写一个reactive就三个设计理念。
- 1.对象代理。(基于Proxy)。
- 2.收集副作用函数。在get的时候触发。
- 3.触发副作用函数。在set的时候触发。
希望这篇文章能帮助你真正理解Vue响应式的精髓,下次面试被问到"Vue如何实现响应式"时,可以自信地从Proxy讲到依赖收集,从性能优化谈到设计模式!
完整代码如下:
js
// reactive.js
// map es6 新增数据结构 弱引用 hashMap
// key取value json 的key 只能是字符串,map 可以是对象
import {
mutableHandlers,
shallowHandlers
} from './baseHandlers'
export const reactiveMap = new WeakMap(); // 全局依赖地图
export const shallowReactiveMap = new WeakMap(); // 浅依赖
// target: 代理的目标对象
export const reactive = (target) => {
// 返回代理对象
return createReactiveObject(target,reactiveMap,mutableHandlers)
}
// proxyMap 代理地图 proxyHandlers 代理处理函数
function createReactiveObject(target,proxyMap,proxyHandlers) {
if (typeof target !== 'object') {
console.warn('target must be an object');
return target
}
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy // 存在,直接返回
}
const proxy = new Proxy(target,proxyHandlers)
proxyMap.set(target,proxy)
return proxy
}
export const shallowReactive = (target) => {
return createReactiveObject(target,shallowReactiveMap,shallowHandlers)
}
注意增加shallow浅代理哦
js
import { track,trigger} from "./effect.js"
const get = createGetter(); // 创建get方法
const set = createSetter();
// 收集 shallow 浅显
function createGetter(shallow = false) {
// {a:1,b:2,c:{d:{e:1,f:2}} 递归
return function get(target,key,receiver) {
console.log('target被读取值',target,key);
track(target,"get",key);
// 设置源对象键值 target[key] = value
let res = target[key];
return res;
}
}
function createSetter() {
return function set(target,key,value,receiver) {
const res = Reflect.set(target,key,value,receiver)
trigger(target,"set",key);
return res;
}
}
export const mutableHandlers = {
get,
set
}
const shallowReactiveGet = createGetter(true)
export const shallowReactiveHandlers = {
get:shallowReactiveGet,
set
}
订阅,进行收集依赖和触发依赖
js
let activeEffect = null; // 当前effect
let targetMap = new WeakMap(); // 存储所有响应式对象
// 注册一个响应式副作用函数fn,立即执行并且返回自身
export function effect(fn) {
// 立即执行一次 返回函数
console.log(fn,'......///');
// 赋值全局activeEffect
const effectFn = () => {
try{
activeEffect = effectFn
return fn() // 搜集依赖
} catch (e) {
console.log('effectFn error',e);
}
}
effectFn()
return effectFn
}
// 依赖收集
export function track(target,type,key) {
console.log('触发track -> target:type(get |{{}} | onMounted)',target,type,key);
let depsMap = targetMap.get(target); // 收集依赖
if (!depsMap) {
// 没有depsMap 创建一个Map
targetMap.set(target,depsMap = new Map())
}
// console.log(depsMap,'depsMap????');
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect); // 将当前副作用函数添加到依赖集合中
}
// 依赖触发
export function trigger(target,type,key) {
console.log('触发trigger -> target:type(get |{{}} | onMounted)',target,type,key);
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if(dep) {
dep.forEach(effectFn => {
effectFn(); // 触发所有副作用函数
});
}
}