前言
最近在学习Vue3的核心知识,觉得 computed 这个Vue中重要的功能的实现逻辑很巧妙很有意思,想加深自己的印象,所以想写个笔记,记录一下。
首先,computed 功能特点之一是,会把计算结果缓存起来,当所依赖的值发生变化,下一次获取值的时候才会重新进行计算。
例子
根据computed的特点,我们将实现以下例子:
// 创建响应式数据
let user = reactive({ age: 10 });
let count = 0;
function getUserAge(){
count++; // 记录函数运行的次数
console.log(`count: ${count}`);
return user.age;
}
// 创建computed数据
let myUser = computed(getUserAge);
console.log(count === 0); // true - 函数没有执行过
myUser.value;
console.log(count === 1); // true - 首次获取依赖值,需执行函数来获取
myUser.value;
console.log(count === 2); // false - 所依赖的值未发生变化,再次获取,返回的是缓存值,未执行函数
user.age = 30;
console.log(count); // 1 - 所依赖的值虽然发生变化,但未进行获取myUser的值,所以不会执行函数
myUser.value;
console.log(count); // 2 - 所依赖的值发生变化,且再次取值,会执行函数
实现
- 响应式数据
reactive 函数用于创建响应式数据,vue3中利用 Proxy API来实现,代理对给定对象的操作,在属性 get 的时候收集依赖,在属性 set 的时候触发依赖,这就是vue的响应式原理的大致实现方式。
function reactive(obj){
return new Proxy(obj, {
get(target, key){
// 收集依赖
track(target, key);
let res = Reflect.get(target, key);
return res;
},
set(target, key, newValue){
let res = Reflect.set(target, key, newValue);
// 触发依赖
trigger(target, key);
return res;
}
})
}
- 依赖收集/触发
let targetMap = new Map(); // 收集每个响应式对象
let activeEffect = undefined; // 当前需要收集的依赖
class ReactiveEffect{
_fn = undefined;
_scheduler = undefined;
deps = undefined;
constructor(fn, scheduler){
this._fn = fn;
this._scheduler = scheduler;
this.deps = new Set();
}
run(){
activeEffect = this;
let res = this._fn();
activeEffect = undefined;
return res;
}
}
/**
* 收集依赖
* target 目标对象
* key 对象的key
*/
function track(target, key){
if(!activeEffect) return;
// 获取对象对应的map
let depsMap = targetMap.get(target);
if(!depsMap) targetMap.set(target, ( depsMap = new Map() ));
// 获取key对应的存放依赖的集合
let deps = depsMap.get(key);
if(!deps) depsMap.set(key, ( deps = new Set() ));
// 收集依赖
deps.add(activeEffect);
// 反向收集
activeEffect.deps.add(deps);
}
/**
* 触发依赖
* target 目的对象
* key 对象的key
*/
function trigger(target, key){
let depsMap = targetMap.get(target);
let deps = depsMap.get(key);
// 触发依赖
deps.forEach(effect => {
effect._scheduler ? effect._scheduler() : effect.run();
})
}
- 计算属性
class ComputedImpl{
_dirty = true;
_value = undefined;
_effect = undefined;
constructor(getter){
this._effect = new ReactiveEffect(getter, () => { this._dirty = true });
}
get value(){
if(this._dirty){
this._value = this._effect.run();
this._dirty = false;
}
return this._value;
}
}
/**
* 创建computed数据
* getter 目标函数
*/
function computed(getter){
return new ComputedImpl(getter);
}
结果
总文件
let targetMap = new Map(); // 收集每个响应式对象
let activeEffect = undefined; // 当前需要收集的依赖
function reactive(obj){
return new Proxy(obj, {
get(target, key){
// 收集依赖
track(target, key);
let res = Reflect.get(target, key);
return res;
},
set(target, key, newValue){
let res = Reflect.set(target, key, newValue);
// 触发依赖
trigger(target, key);
return res;
}
})
}
class ReactiveEffect{
_fn = undefined;
_scheduler = undefined;
deps = undefined;
constructor(fn, scheduler){
this._fn = fn;
this._scheduler = scheduler;
this.deps = new Set();
}
run(){
activeEffect = this;
let res = this._fn();
activeEffect = undefined;
return res;
}
}
/**
* 收集依赖
* target 目标对象
* key 对象的key
*/
function track(target, key){
if(!activeEffect) return;
// 获取对象对应的map
let depsMap = targetMap.get(target);
if(!depsMap) targetMap.set(target, ( depsMap = new Map() ));
// 获取key对应的存放依赖的集合
let deps = depsMap.get(key);
if(!deps) depsMap.set(key, ( deps = new Set() ));
// 收集依赖
deps.add(activeEffect);
// 反向收集
activeEffect.deps.add(deps);
}
/**
* 触发依赖
* target 目的对象
* key 对象的key
*/
function trigger(target, key){
let depsMap = targetMap.get(target);
let deps = depsMap.get(key);
// 触发依赖
deps.forEach(effect => {
effect._scheduler ? effect._scheduler() : effect.run();
})
}
class ComputedImpl{
_dirty = true;
_value = undefined;
_effect = undefined;
constructor(getter){
this._effect = new ReactiveEffect(getter, () => { this._dirty = true });
}
get value(){
if(this._dirty){
this._value = this._effect.run();
this._dirty = false;
}
return this._value;
}
}
/**
* 创建computed数据
* getter 目标函数
*/
function computed(getter){
return new ComputedImpl(getter);
}
// 创建响应式数据
let user = reactive({ age: 10 });
let count = 0;
function getUserAge(){
count++; // 记录函数运行的次数
return user.age;
}
let myUser = computed(getUserAge);
console.log(count === 0); // true - 函数没有执行过
myUser.value;
console.log(count === 1); // true - 首次获取依赖值,需执行函数来获取
myUser.value;
console.log(count === 2); // false - 所依赖的值未发生变化,再次获取,返回的是缓存值,未执行函数
user.age = 30;
console.log(count); // 1 - 所依赖的值虽然发生变化,但未进行获取myUser的值,所以不会执行函数
myUser.value;
console.log(count); // 2 - 所依赖的值发生变化,且再次取值,会执行函数