前言:通过自己实现一个简单的响应式系统,来帮助我们理解Vue的响应式原理。
其实响应式系统的本质就是,收集跟响应式数据有关的依赖函数,并且在响应式数据发生改变时触发这些收集到的依赖函数。
要实现的基本功能
我们需要有一个数组来保存所有依赖函数,也需要一个watchFn来收集响应式函数,
//保存所有在变量改变需要执行的依赖函数的数组
let reactiveFns = []
//用于收集传入的依赖函数
function watchFn(fn){
reactiveFns.push(fn)
}
// 先假设这个obj是响应式数据
let obj = {
name:"wjj",
age:10
}
//name发生改变要执行的依赖函数
watchFn(function(){
console.log('这是依赖函数1'+obj.name)
})
//name发生改变要执行的函数
watchFn(function(){
console.log('这是依赖函数2'+obj.name)
})
//修改响应式数据
obj.name = 'jzsp'
//当name改变时,执行响应式函数
reactiveFns.forEach(fn=>{
fn()
})
基本响应式的封装
首先我们用类来封装,因为我们可能用到多个响应式数据,不可能把所有依赖函数都保存在同一个数组中。 一个实例保存与某个响应式数据有关的依赖函数(其实就是用到响应式数据的函数)
//用于收集某个属性的所有响应式函数
class Depend{
constructor() {
this.reactiveFns = []
}
//添加响应式函数
addDepend(fn){
this.reactiveFns.push(fn)
}
//封装的notify方法,用于执行该实例中保存的所有响应式函数
notify(){
this.reactiveFns.forEach(fn => {
fn()
})
}
}
let obj = {
name:"wjj",
age:10
}
const nameDepend = new Depend()
function watchFn(fn){
depend.addDepend(fn)
}
//name发生改变要执行的依赖函数
watchFn(function(){
console.log('这是依赖函数1'+obj.name)
})
//name发生改变要执行的函数
watchFn(function(){
console.log('这是依赖函数2'+obj.name)
})
//修改响应式数据
obj.name = 'jzsp'
//当name改变时,执行响应式函数
nameDepend.notify()
用Proxy监听对象属性的变化
因为我们总不能每次都手动调用notify函数来触发依赖。所以我们可以利用Proxy代理的set监听,在通过Proxy代理给对象的属性设置值的时候自动调用notify函数。
class Depend {
constructor() {
this.reactiveFn = [];
}
addDepend(fn) {
this.reactiveFn.push(fn);
}
notify() {
this.reactiveFn.forEach((item) => item());
}
}
let obj = {
name: "wjj",
age: 20,
};
let nameDepend = new Depend();
function watchFn(fn) {
nameDepend.addDepend(fn);
}
//监听对象属性的变化:Vue3(Proxy) / Vue2(Object.defineProperty)
const objProxy = new Proxy(obj, {
get(target, p, receiver) {
return Reflect.get(target, p, receiver);
},
set(target, p, value, receiver) {
Reflect.set(target, p, value, receiver);
//当name改变时,执行响应式函数
nameDepend.notify();
},
});
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数1" + objProxy.name);
});
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数2" + objProxy.name);
});
objProxy.name = "jzsp";
使用weakMap来保存每个对象的属性的Depend实例
我们在这一步修改了watchFn函数,默认将传入的函数执行一次,在执行的时候如果用到了响应式数据,就会进入对应的Proxy的get方法。
同时我们封装了getDepend方法,用WeakMap保存每个对象的map(保存的内容是<obj,map>),每个对象的map(保存的内容是<key,depend>)里又保存了每个属性的depend实例。通过getDepend方法来获取target对象的key属性的depend实例。
在get方法中,获取对应的depend实例,收集依赖。
在set方法中,获取对应的depend实例,触发依赖。
let globalFn;
class Depend {
constructor() {
this.reactiveFn = [];
}
addDepend(fn) {
this.reactiveFn.push(fn);
}
notify() {
this.reactiveFn.forEach((item) => item());
}
}
let obj = {
name: "wjj",
age: 20,
};
//收集依赖的函数,传入的函数默认会执行一次,如果用到了响应式数据,会进入对应Proxy的get方法中
function watchFn(fn) {
globalFn = fn;
fn();
globalFn = null;
}
let wm = new WeakMap();
//获取target对象对应属性的depend实例
function getDepend(target, key) {
let map = wm.get(target);
if (!map) {
map = new Map();
wm.set(target, map);
}
let depend = map.get(key);
if (!depend) {
depend = new Depend();
map.set(key, depend);
}
return depend;
}
//监听对象属性的变化:Vue3(Proxy) / Vue2(Object.defineProperty)
const objProxy = new Proxy(obj, {
get(target, p, receiver) {
//获取target对象的对应属性的depend实例
let depend = getDepend(target, p);
// 收集依赖
depend.addDepend(globalFn);
return Reflect.get(target, p, receiver);
},
set(target, p, value, receiver) {
Reflect.set(target, p, value, receiver);
//当name改变时,执行响应式函数
let depend = getDepend(target, p);
//触发依赖
depend.notify();
},
});
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数1" + objProxy.name);
});
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数2" + objProxy.name);
});
setTimeout(() => {
objProxy.name = "jzsp";
}, 1000);
进一步封装,创建响应式对象的reactive函数
let globalFn;
class Depend {
constructor() {
this.reactiveFn = [];
}
addDepend(fn) {
this.reactiveFn.push(fn);
}
notify() {
this.reactiveFn.forEach((item) => item());
}
}
// 创建响应式对象
let obj = reactive({
name: "wjj",
age: 20,
});
function watchFn(fn) {
globalFn = fn;
fn();
globalFn = null;
}
let wm = new WeakMap();
function getDepend(target, key) {
let map = wm.get(target);
if (!map) {
map = new Map();
wm.set(target, map);
}
let depend = map.get(key);
if (!depend) {
depend = new Depend();
map.set(key, depend);
}
return depend;
}
//传入一个对象,返回这个对象的代理
function reactive(obj) {
return new Proxy(obj, {
get(target, p, receiver) {
getDepend(target, p).addDepend(globalFn);
return Reflect.get(target, p, receiver);
},
set(target, p, value, receiver) {
Reflect.set(target, p, value, receiver);
getDepend(target, p).notify();
},
});
}
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数1" + obj.name);
});
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数2" + obj.name);
});
setTimeout(() => {
obj.name = "jzsp";
}, 1000);
Vue2的响应式原理
vue2的响应式跟vue3的区别在于,创建响应式对象的地方。在vue2中是通过遍历传入的obj对象的key,对每个key调用Object.defineProperty(obj,key,options)来给obj对象添加存取属性描述符。
let globalFn;
class Depend {
constructor() {
this.reactiveFn = [];
}
addDepend(fn) {
this.reactiveFn.push(fn);
}
notify() {
this.reactiveFn.forEach((item) => item());
}
}
let obj = reactive({
name: "wjj",
age: 20,
});
//收集依赖的
function watchFn(fn) {
globalFn = fn;
fn();
globalFn = null;
}
let wm = new WeakMap();
//获取target对象对应属性的depend实例
function getDepend(target, key) {
let map = wm.get(target);
if (!map) {
map = new Map();
wm.set(target, map);
}
let depend = map.get(key);
if (!depend) {
depend = new Depend();
map.set(key, depend);
}
return depend;
}
//监听对象属性的变化:Vue3(Proxy) / Vue2(Object.defineProperty)
function reactive(obj) {
Object.keys(obj).forEach((key) => {
let v = obj[key];
Object.defineProperty(obj, key, {
get() {
getDepend(obj, key).addDepend(globalFn);
return v;
},
set(newV) {
v = newV;
getDepend(obj, key).notify();
},
});
});
return obj;
}
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数1" + obj.name);
});
//name发生改变要执行的函数
watchFn(function () {
console.log("这是依赖函数2" + obj.name);
});
setTimeout(() => {
obj.name = "jzsp";
}, 1000);
以上代码都可以直接复制运行,欢迎大家在评论区多多指正哈~