写在前面:本文为个人在日常工作和学习中的一些总结,便于后来查漏补缺,非权威性资料,请带着自己的思考^-^。
前文链接:了解一下Vue - [Vue是怎么实现响应式的(一)]
了解一下Vue - [Vue是怎么实现响应式的(二)]
前面对于响应式有了一些些了解,这里尝试自己写一遍(抄一遍)
设对象data为要被定义的响应式对象,key为data中的属性
- 通过defineProperty为data中的每一个属性key定义getter/setter,这样在data[key]被引用和被赋值时将触发相应的操作,这个是响应式实现的基础;
- dep对象,作为依赖实体,其包含依赖添加、依赖者(watcher)添加、变更通知(notify)等方法,用于依赖收集、变更分发,本身包含观察者列表;
- watcher对象,观察者对象,包含依赖收集、更新方法;用于依赖收集、更新,包含依赖列表;
Dep构造函数
let uid = 0;
class Dep {
constructor() {
this.id = ++uid;
this.subs = [];
}
addSub(target) { // 将target添加进观察者列表
this.subs.push(target);
}
depend() { //
if (Dep.target) {
Dep.target.addDep(this);
}
}
removeSub(target) {
this.subs.splice(this.subs.findIndex(_ => _.id == target.id), 1);
}
notify() {
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
Dep.target = null; // 当前处于激活状态的watcher
const targetStack = []; // 存放watcher的栈
const pushTarget = target => { // 更新当前激活的Dep.target
if (Dep.target) {
targetStack.push(target); // 将target(watcher)压入栈中
}
Dep.target = target;
};
const popTarget = () => { // 从watcher栈中弹出顶部watcher
Dep.target = targetStack.pop();
};
watcher构造函数
class Watcher {
constructor(getter, options = {}) {
this.deps = []; // 依赖列表
this.newDeps = []; // 最近一次添加的依赖列表
this.depIds = new Set(); // 依赖ids列表
this.newDepIds = new Set(); // 最近一次添加的依赖ids列表
this.getter = getter; //
this.lazy = !!options.lazy; // 懒依赖,在首次实例化的时候不执行getter
this.dirty = this.lazy; // 脏值标识,主要用在computed计算时;
this.lazy ? undefined : this.get();
}
get() {
pushTarget(this); // 将当前watcher作为激活的watcher对象,并推入targetStack栈中
const value = this.getter();
popTarget(); // 将当前watcher置为栈中上一个watcher
this.cleanupDeps(); // 依赖整理,主要用来整理this.deps、this.depIds
return value;
}
addDep(dep) {
if (!this.newDepIds.has(dep.id)) {
this.newDepIds.add(dep.id);
this.newDeps.push(dep);
if (!this.depIds.has(dep.id)) {
dep.addSub(this);
}
}
}
cleanupDeps() {
let i = this.deps.length;
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) { // 如果新的依赖列表中不再包含之前的依赖项,则调用dep.removeSub方法,将当前watcher从dep.subs列表中移除
dep.removeSub(this);
}
}
[this.deps, this.newDeps] = [this.newDeps, this.deps];
this.newDeps.length = 0;
[this.depIds, this.newDepIds] = [this.newDepIds, this.depIds];
this.newDepIds.clear();
}
evaluate() {
this.value = this.get();
this.dirty = false;
}
update() {
if (this.lazy) {
this.dirty = true;
} else {
new Promise((resolve) => {
resolve();
}).then(() => {
this.get();
});
}
}
depend() { // 将当前watcher的依赖添加到当前Dep.target的依赖列表中
const deps = this.deps;
for (let i = 0; i < deps.length; i++) {
deps[i].depend();
}
}
}
defineReactive 方法
用来为data[key]定义getter/setter
const defineReactive = (target, key, val) => {
const dep = new Dep(); // 这里实例化dep对象,用于在getter/setter触发的时候访问该对象进行依赖收集等操作,本质上来说当前实例化的dep和当前的data[key]一一对应了
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.depend(); // 依赖添加
}
return val;
},
set(newVal) {
if (val === newVal) return;
val = newVal;
dep.notify();
},
});
};
data、computed初始化
const initData = data => {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
defineReactive(vm, keys[i], data[keys[i]]);
}
};
const initComputed = computed => {
const keys = Object.keys(computed);
for (let i = 0; i < keys.length; i++) {
const userDef = computed[keys[i]].bind(vm);
const watcher = new Watcher(userDef, { lazy: true });
Object.defineProperty(vm, keys[i], {
enumerable: true,
configurable: true,
get() {
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value;
}
},
});
}
};
模拟页面渲染:
<!-- html -->
<body>
<div id="app"></div>
<button id="btn">更新</button>
</body>
const data = initData(vm.data);
const computed = initComputed(vm.computed);
const updateComponent = () => {
const app = document.querySelector('#app');
var a = vm.current; // 引用vm.current
var b = vm.computedCurrent; // 应用computed
app.innerHTML = `data: <i>${a}</i> computed: <strong>${b}</strong>`;
};
defineReactive(vm, 'current', vm.current); // 为vm.current定义getter/setter
const watcher = new Watcher(updateComponent);
可以看到点击按钮,更新vm中的current属性,页面成功进行了更新。。。
流程分析
初始化data属性getter/setter --> 实例化watcher,带有update方法、addDep方法 --> watcher.get方法执行进行依赖收集(该方法中被引用到的data属性视为当前watcher的依赖) --> watcher依赖收集的过程中被依赖的data属性也会进行观察者收集 --> data属性更新 --> 通知watcher,update方法调用 --> 新一轮的依赖收集,新旧依赖比较,新依赖相对旧依赖缺失的从依赖列表中删除,新增的加入依赖列表同时将观察者watcher添加进该依赖的subs观察者列表中 --> 执行业务代码(视图更新);
对于computed来说,它既是观察者,也是依赖;视图更新watcher依赖computed,computed依赖data;
对于watch来说,它是观察者,依赖要watch的data或者computed,依赖更新时会notify(通知)它,执行相应的方法;
原理大抵如此,细节还有很多
THE END
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。