当把这张图看懂时,就能完全清楚的理解响应式了。
以前有过一些vue2响应式的学习,是粗浅的了解。只知道Object.defineProperty的方法能劫持属性的变化。并不了解dep、watcher与每个属性的关系。
watcher:个人理解,watcher就是我的某一个属性更新的时候,需要做的一些更新操作。例如total = price * count,当price和count更新时,必须让total = price * count这句代码执行才能更新total,watcher就是保存这句代码的。
dep:个人理解,dep是观察模式的重点,它里面需要维护一个任务队列,当某一个属性更新时,就需要把该属性相关的更新任务执行一遍。当然你可以让一个dep去维护所有属性的队列,当时那样是浪费的并不合适,所以是一个属性关联需要实例化一个dep。
example:<h1>{{ msg }}</h1><input type="text" v-model="msg" />
在这个很简单的模版匹配和v-model例子中,当在get时进行Dep实例的数组中添加watcher,set时进行Dep的数组轮询执行watcher。这样很合理也很好理解。但是怎样在get的时候添加watcher呢?此时需要一个全局变量target,当模版匹配时(Compiler过程)中将watcher实例化,并赋值给target,再在get的时候添加到dep的数组中。watcher其实就是你需要数据变化时所需要更新模版的一些操作,例如更新文本节点或者更新input的value。
附上一些简单的代码,欢迎讨论
const utils = {
getValue(key, vm) {
return vm.$data[key.trim()];
},
setValue(key, vm, newValue) {
vm.$data[key] = newValue;
},
textUpdater(node, text) {
node.textContent = text;
},
modelUpdater(node, value) {
node.value = value;
},
model(node, dataKey, vm) {
const initValue = this.getValue(dataKey, vm);
new Watcher(dataKey, vm, (newValue) => {
this.modelUpdater(node, newValue);
});
node.addEventListener("input", (e) => {
const newValue = e.target.value;
this.setValue(dataKey, vm, newValue);
});
this.modelUpdater(node, initValue);
},
text(node, value, vm) {
let result;
if (value.includes("{{")) {
result = value.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(args[1], vm, (newValue) => {
this.textUpdater(node, newValue);
});
return this.getValue(args[1], vm);
});
} else {
result = this.getValue(value, vm);
}
this.textUpdater(node, result);
},
on(node, eventName, vm, eventType) {
const f = vm.$options.methods[eventName];
node.addEventListener(eventType, f.bind(vm), false)
},
};
class Watcher {
constructor(expr, vm, cb) {
this.expr = expr;
this.vm = vm;
this.cb = cb;
this.oldValue = this.getOldValue();
}
getOldValue() {
Dep.target = this;
const oldValue = utils.getValue(this.expr, this.vm);
Dep.target = null;
return oldValue;
}
update() {
const newValue = utils.getValue(this.expr, this.vm);
if (newValue !== this.oldValue) {
this.cb(newValue);
}
}
}
class Dep {
constructor() {
this.subsribes = [];
}
addWatcher(watcher) {
this.subsribes.push(watcher);
}
notify() {
this.subsribes.forEach((sub) => sub.update());
}
}
class Compiler {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
const fragment = this.compilerFragment(this.el);
this.compiler(fragment);
this.el.appendChild(fragment);
}
// 避免多次操作dom 用碎片来操作,最后一次dom操作达到多次操作的结果
compilerFragment(el) {
const frag = document.createDocumentFragment();
let firstChild = null;
// 循环来将原来dom插入到frag中
while ((firstChild = el.firstChild)) {
frag.appendChild(firstChild);
}
return frag;
}
compiler(el) {
const childNodes = Array.from(el.childNodes);
childNodes.forEach((childNode) => {
if (this.isElementNode(childNode)) {
this.compilerElement(childNode);
}
if (this.isTextNode(childNode)) {
this.compilerText(childNode);
}
if (childNode && childNode.childNodes.length > 0) {
// 元素有子元素 需要递归遍历
this.compiler(childNode);
}
});
}
compilerElement(node) {
const attrs = Array.from(node.attributes);
attrs.forEach((attr) => {
const { name, value } = attr;
if (this.isDirector(name)) {
// example: v-model v-on: v-bind
const [, directive] = name.split("-");
const [compilerKey, eventType] = directive.split(":");
utils[compilerKey](node, value, this.vm, eventType);
} else if(this.isEvent(name)) {
// @click="handleClick" @change="handleChange" .......
const eventType = name.slice(1)
utils.on(node, value, this.vm, eventType);
}
});
}
compilerText(node) {
const textContent = node.textContent;
if (/^\{\{\s+(.+)\s+\}\}$/.test(textContent)) {
utils.text(node, textContent, this.vm);
}
}
isElementNode(el) {
return el.nodeType === 1;
}
isTextNode(el) {
return el.nodeType === 3;
}
isDirector(name) {
return /v-(.+)/.test(name);
}
isEvent(name) {
return /@(.+)/.test(name);
}
}
class Observer {
constructor(data) {
this.observer(data);
}
observer(data) {
if (data && typeof data === "object") {
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key]);
});
}
}
defineReactive(data, key, value) {
this.observer(data[key]);
const dep = new Dep();
Object.defineProperty(data, key, {
get() {
const target = Dep.target;
target && dep.addWatcher(target);
return value;
},
set: (newVal) => {
if (newVal !== value) {
// this.xxxData = {} 需要遍历一下
this.observer(newVal);
value = newVal;
dep.notify();
}
},
});
}
}
class Vue {
constructor(options) {
this.$options = options;
this.$el = options.el;
this.$data = options.data;
new Observer(this.$data);
new Compiler(this.$el, this);
this.proxyData(this.$data);
}
proxyData(data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(newVal) {
this.$data[key] = newVal;
},
});
});
}
}