Vue的双向数据绑定本质上就是数据劫持和发布订阅。无论是Vue2.x的Object.defineProperty()还是Vue3的proxy,实现响应式原理都是一致的,只是方法发生了改变。
1. 无响应式
let a = 10;
let b = a + 10;
console.log('b :>> ', b); // 20
a = 20;
console.log('b :>> ', b); // 20
b = a + 10;
console.log('b :>> ', b); // 30
2. 基础响应式(本质就是发布订阅,依赖收集)
let curEffect = null;
class Dep {
constructor(val) {
// 收集的依赖不会重复,所以使用set
this.effects = new Set();
this._val = val;
}
get value() {
this.depend();
return this._val;
}
set value(newVal) {
if (Object.is(this._val, newVal)) return;
this._val = newVal;
this.notify();
}
depend() {
// 因为curEffect可能不存在,一定判断否则notify报错。
if (curEffect) {
// 做一个优化:如果依赖存在就不收集
if (!this.effects.has(curEffect)) {
this.effects.add(curEffect);
}
}
}
notify() {
this.effects.forEach(fn => fn());
}
}
function effectWatch(effct) {
curEffect = effct;
effct();
curEffect = null;
}
let dep = new Dep(10);
let b;
effectWatch(() => {
b = dep.value + 10;
console.log('b :>> ', b); // 20 30
})
dep.value = 20;
3. reactive proxy
// 用于存储对象 key值为对象 使用WeakMap便于垃圾回收
let targetMap = new WeakMap();
// targetMap -> {
Object: {
Object[key]: Dep,
...
},
...
}
function getDep(target, key) {
// 获取map中的值
let depsMap = targetMap.get(target);
// 如果值不存在,则添加
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
// 给对象的每个key值为Dep
let dep = depsMap.get(key);
// 如果值不存在,则添加
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
function reactive(raw) {
// 如果不是对象就直接返回
if (typeof raw === 'object' && raw !== null) {
return new Proxy(raw, {
get(target, key) {
const dep = getDep(target, key);
dep.depend();
const value = Reflect.get(target, key);
// 如果值的类型还是对象,递归。否则无法全部代理到
// 源码中还有shallowX方法,只响应式第一层
if(value && typeof value === 'object') return reactive(value);
return value;
},
set(target, key, value) {
const oldVal = Reflect.get(target, key);
// 如果值相同,则直接返回
if (Object.is(value, oldVal)) return;
Reflect.set(target, key, value);
const dep = getDep(target, key);
dep.notify();
}
});
}
else {
return raw;
}
}
const user = reactive({
name: '朝九',
age: 18,
count: {
num: 1
}
});
let nextYearAge;
let newCount;
effectWatch(() => {
nextYearAge = user.age + 1;
newCount = user.count.num + 1;
console.log('newCount :>> ', newCount); // 19 21
console.log('nextYearAge :>> ', nextYearAge); // 2 21
})
user.age = 20;
user.count.num = 20;
4. ref
// ref定义的对象也需要通过.value找到,但我这里只做简单处理,以实现功能。
function ref(value) {
if (value !== null && typeof value === 'object') {
return reactive(value);
}
// vue3会把所有的响应式最终作为一个对象使用
return new Dep(value);
}
let count = ref(1);
let doubleCount;
effectWatch(() => {
doubleCount = count.value * 2;
console.log('doubleCount :>> ', doubleCount); // 2 4
})
count.value = 2;
let list = ref([{num: 1}, {num: 2}]);
let sum;
effectWatch(() => {
sum = 0;
list.forEach(item => {
sum += item.num;
})
console.log('sum :>> ', sum); // 3 4
})
list[0].num = 2;