vue通过使用Object.defineProperty实现数据的响应式更新,(vue3使用proxy),响应式本质是数据和函数的关联,数据更新会执行依赖该数据的函数
下面自己写一个dmeo模拟下响应式的实现
//例如更新如下dom
<div>
姓名:<span class="data"></span>
</div>
//通过修改如下数据更新dom
const obj = {
data: '张三';
}
//模拟render方法, 简单实现dom更新
function setName() {
document.getElementsByClassName('data')[0].innerHTML = obj.data;
}
建立了dom,数据,还模拟了render方法,接下来通过执行observer方法实现数据响应式更新
function observer(obj) {
for (const key in obj) {
let value = obj[key];
Object.defineProperty(obj, key, {
get: function () {
//当获取数据的时候执行get方法,同时收集使用了该数据的函数
return value;
},
set: function (newValue) {
//改变该数据的时候执行set方法,同时派发跟新,执行之前收集的函数,实现dom的渲染
value = newValue;
setName();
},
});
}
}
当调用set的时候执行了render方法(这里是setName),这个render方法就是依赖该数据的方法,也就是get里面收集的方法,但是get方法只知道obj数据被读取了,并不知道该数据是在哪个函数里面读取的,所以需要对当前正在执行的函数进行监控
//当执行render的时候,修改当前window的_func属性为当前函数,这个get函数里面就可以通过读取window._func知道当前哪个函数在执行
window._func = render;
render();
window._func = null;
//这样每执行一个函数都需要这样使用window._func包裹一层,函数多了会很冗余,可以单独封装个函数,把要执行的函数交给该函数
function getDepFn(fn){
window._func = fn;
fn();
window._func = null;
}
然后再重写observer方法
function observer(obj) {
for (const key in obj) {
let value = obj[key];
const arr = []; //收集依赖该数据的函数
Object.defineProperty(obj, key, {
get: function () {
//当获取数据的时候执行get方法,同时收集使用到该数据的函数
if(!arr.includes(window._func) && window._func){
arr.push(window._func);
}
return value;
},
set: function (newValue) {
//改变该数据的时候执行set方法,同时派发跟新,执行之前收集的函数,实现dom的渲染
value = newValue;
arr.forEach(fn => {
fn();
});
},
});
}
}
上面只是简单模拟了vue的响应式实现实现,实际的源码会复杂的多,有许多细节需要处理,这里不就详细展开。