vue2 PK vue3
要说到vue2和vue3的响应式是怎么实现的,大家都能大概回答出来
- vue2的响应式是基于Object.defineProperty来实现的
- vue3的响应式是基于Proxy实现的
抽象的回答却也说出了两个版本的核心原理,就是提现在Object.defineProperty和Proxy上
vue2
先简单举个例子,来看下vue2的object.defineProperty
function
Object.defineProperty(obj, key, {
get() {
console.log(`访问了${value}属性`)
return value
},
set(val) {
console.log(`将${value}改成了${val}`)
value = val;
}
})
}
const data = {
name: 'gaoyan',
age: 28
};
Object.keys(data).forEach(key => {
reactive(data, key, data[key])
})
console.log(data.name);
data.name='Mrs.gao'
console.log(data.name);
-----打印结果
访问了gaoyan属性
gaoyan
将gaoyan改成了Mrs.gao
访问了Mrs.gao属性
Mrs.gao
但是如果我新增一个属性呢
js
data.hobby='coding';
console.log(data.hobby);
data.hobby='write';
console.log(data.hobby);
-----打印结果
coding
write
这时我们会发现我们新增了一个属性,并且对这个属性进行了读取和修改,但是没有触发get和set,也就是说Object.defineProperty不会对新增的对象进行监听,需要额外使用Vue.$set来把新增的属性设置为响应式对象
vue3响应式原理
js
let name='高高',age=22,money=1;
let myself = `${name}今年${age}岁,存款${money}元`
console.log(myself); // 高高今年22岁,存款1元
money=100;
console.log(myself); // 高高今年22岁,存款1元
看完上面一段代码,我们发现,当变量money由1变成10以后,myself的值却没有发生变化,那如果想要按照我们的预期让myself跟着money的变化而变化,我们需要把myself再执行一次,如下图
let name='高高',age=22,money=1; let myself = ${name}今年${age}岁,存款${money}元
console.log(myself); // 高高今年22岁,存款1元
money=100;
myself = ${name}今年${age}岁,存款${money}元
;
console.log(myself); // 高高今年22岁,存款100元
由此可见,每次money改变就要执行一次myself,才能是的myself更新,那我们来封装一个effect使得写法更加优雅
js
let name = '高高', age = 22, money = 1;
let myself = '';
const effect = () => myself = `${name}今年${age}岁,存款${money}元`
effect()
console.log(myself); // 高高今年22岁,存款1元
money = 100;
effect()
console.log(myself); // 高高今年22岁,存款100元
但是如果我们再增加一个变量herSelf,就得再写一个effect,然后每次执行一次,一旦增加的变量变多,就得写很多很多的effect
track和trigger
针对上面很多effect的问题,我们用track函数把所有依赖moeny变量的effect函数都搜集起来,放在dep里,不要重复搜集依赖,所以dep用Set自动去重,搜集完以后,只要money发生变化,就执行trigger函数来通知所有依赖money的effect函数执行,实现更新
js
let name = '高高', age = 22, money = 1;
let myself = '';
let herSelf = ''
const dep = new Set();
const effect = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => herSelf = `${age}岁的${name}居然有${money}元`
const track = (fn) => {
// 搜集依赖
dep.add(fn)
}
const trigger = () => {
// 执行依赖
dep.forEach(fn => fn())
}
// effect()
// effect2()
track(effect)
track(effect2)
trigger()
console.log(myself); // 高高今年22岁,存款1元
console.log(herSelf); //22岁的高高居然有1元
money = 100;
// effect()
// effect2()
trigger()
console.log(myself); // 高高今年22岁,存款100元
console.log(herSelf); //22岁的高高居然有100元
到目前为止,我们实现了基本数据类型的响应式,那如果是对象呢,我们可以暂时把person对象里的name和car看成两个变量,他们各自有各自依赖变量,关系如下图,那person有两个属性,所以他拥有两个dep,该怎么存储这两个呢,我们可以用Map来存储
js
let str1 = '';
let str2 = '';
const depsMap = new Map()
const effect = () => str1 = `我的名字叫${person.name}`
const effect2 = () => str2 = `我的车是${person.car}`
function track(key) {
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, dep = new Set())
}
if (key === 'name') {
dep.add(effect)
} else {
dep.add(effect2)
}
}
function trigger(key) {
let dep = depsMap.get(key);
if (dep) {
dep.forEach(fn => fn());
}
}
track('name');
track('car');
trigger('name');
trigger('car')
console.log(str1, str2) // 我的名字叫高高 我的车是宾利
person.name = '琳琳';
person.car = '兰博基尼'
trigger('name');
trigger('car')
console.log(str1, str2) // 我的名字叫琳琳 我的车是兰博基尼
console.log(depsMap)
// Map(2) {
// 'name' => Set(1) { [Function: effect] },
// 'car' => Set(1) { [Function: effect2] }
}
如果是多个对象,那么我们就用weakMap来把多个对象进行封装,具体解释如下图
js
const person = { name: '高高', car: '宾利' }
const tool = { dis: '小刀', usage: '裁纸' }
let str1 = '';
let str2 = '';
let str3 = '';
let str4 = '';
const targetMap = new WeakMap();
const effect = () => str1 = `我的名字叫${person.name}`
const effect2 = () => str2 = `我的车是${person.car}`
const effect3 = () => str3 = `工具的名字叫${tool.dis}`
const effect4 = () => str4 = `工具的作用是${tool.usage}`
function track(target, 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 (target === person) {
if (key === 'name') {
dep.add(effect)
} else {
dep.add(effect2)
}
} else {
if (key === 'dis') {
dep.add(effect3)
} else {
dep.add(effect4)
}
}
}
function trigger(target, key) {
let depsMap = targetMap.get(target);
if (depsMap) {
let dep = depsMap.get(key);
dep.forEach(fn => fn());
}
}
track(person, 'name');
track(person, 'car');
track(tool, 'dis');
track(tool, 'usage');
trigger(person, 'name');
trigger(person, 'car');
trigger(tool, 'dis');
trigger(tool, 'usage');
console.log(str1, str2, str3, str4) // 我的名字叫高高 我的车是宾利
person.name = '琳琳';
person.car = '兰博基尼'
tool.dis='锤子'
tool.usage='锤锤锤'
track(person, 'name');
track(person, 'car');
track(tool, 'dis');
track(tool, 'usage');
trigger(person, 'name');
trigger(person, 'car');
trigger(tool, 'dis');
trigger(tool, 'usage');
console.log(str1, str2, str3, str4) // 我的名字叫琳琳 我的车是兰博基尼
console.log(targetMap)
Proxy
上面的例子虽然实现了依赖对象随着数据更新而改变,但是需要手动track搜集依赖,手动trigger通知更新,所以我们用proxy来实现自动搜集和更新
js
// ----------使用proxy自动搜集和更新-------------
function reactive(target) {
const handler = {
get(target, key, receiver) {
track(receiver, key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
trigger(receiver, key)
}
}
return new Proxy(target,handler)
}
const person = reactive({ name: '高高', car: '宾利' })
const tool = reactive({ dis: '小刀', usage: '裁纸' })
解决写死的问题
在之前的track函数中,我们会用if/else去判断,那么每次多加对象就需要多加一些if/else ,所以我们定义了一个全局变量activeEffect,每次搜集依赖的时候就把依赖函数赋值给activeEffect,每次effect一执行,就把增加加入到dep中,那么就需要加一个函数来统一处理传入的函数执行,我们需要改一下effect函数和track
js
let activeEffect = null;
function commonEffect(fn) {
activeEffect = fn;
activeEffect();
activeEffect = null;
}
function track(target, key) {
if(!activeEffect) return
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())
}
dep.add(activeEffect)
// if (target === person) {
// if (key === 'name') {
// dep.add(effect)
// } else {
// dep.add(effect2)
// }
// } else {
// if (key === 'dis') {
// dep.add(effect3)
// } else {
// dep.add(effect4)
// }
// }
commonEffect(effect);
commonEffect(effect2);
commonEffect(effect3);
commonEffect(effect4)
}
到此,我们就实现了一个reactive响应式功能
实现ref
js
let num = ref(5) console.log(num.value) // 5
num会成为一个响应式数据,但是在使用num时需要写num.value才可以用,所以我们把reactive封装下来实现ref
js
function ref1(data) {
return reactive({ value: data })
}
let number = ref1(5);
console.log(number.value) // 5
number.value=100;
console.log(number.value) //100