数据劫持
先对data中的数据进行劫持并挂载到vue实例上,这时每个数据对象都可以模拟是一个订阅者,当数据发生改变,发布者会通知(调用notify方法)每一个订阅者去调用update方法进行更新,然后通过编译器编译渲染到视图上,当视图发生改变了,每个订阅者会向发布者进行订阅,并返回到进行数据更新,进行数据同步(注释仅个人理解,如有不对,请指教)
class Vue {
constructor(options) {
this.$options = options;
this._data = options.data;
this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el
this._proxyData(this._data);
new Observer(this._data)
new Complier(this);
}
_proxyData(data) {
Object.keys(this._data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(nValue) {
if (data[key] === nValue) {
return
}
data[key] = nValue;
}
})
})
}
}
观察者模式
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
if (!data || typeof data !== "object") {
return
}
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(data, key, value) {
let publisher = new Publisher();
let that = this;
this.walk(value);
Object.defineProperty(data, key, {
get() {
Publisher.target && publisher.addSub(Publisher.target);
return value
},
set(nValue) {
if (value === nValue) {
return
}
value = nValue
that.walk(nValue)
publisher.notify()
}
})
}
}
发布者
class Publisher {
constructor() {
this.subs = [];
}
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub);
}
}
notify() {
console.log("notify");
this.subs.forEach(w => {
w.update();
console.log("w.up");
});
}
}
订阅者(观察者)
class Wathcher {
constructor(data, key, cb) {
this.data = data;
this.key = key;
this.cb = cb
Publisher.target = this
this.oldValue = data[key]
}
update() {
console.log("watcher--update");
let newValue = this.data[this.key];
if (newValue === this.oldValue) {
return
}
this.cb(newValue);
}
}
编译器
class Complier {
constructor(vm) {
this.el = vm.$el;
this.data = vm._data;
this.complie(this.el)
}
complie(el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
if (this.isTextNode(node)) {
this.complieText(node)
} else if (this.isElementNode(node)) {
this.complieElement(node)
}
if (node.childNodes && node.childNodes.length > 0) {
this.complie(node)
}
})
}
complieElement(node) {
let attributes = node.attributes
Array.from(attributes).forEach(attr => {
let attrName = attr.name;
if (this.isDirective(attrName)) {
attrName = attrName.substring(2)
console.log(attrName);
let key = attr.value
this.update(node, attrName, key)
console.log(this.data[key]);
}
})
}
update(node, attrName, key) {
let fn = this[attrName + 'Update'];
fn && fn.call(this, node, attrName, key)
console.log(key);
}
textUpdate(node, attrName, key) {
console.log(key);
node.textContent = this.data[key];
console.log(this.data[key]);
new Wathcher(this.data, key, (newValue) => {
console.log("txtup");
node.textContent = newValue
})
}
modelUpdate(node, attrName, key) {
console.log(key);
node.value = this.data[key]
console.log(this.data[key]);
new Wathcher(this.data, key, (newValue) => {
node.value = newValue
})
node.addEventListener('input', () => {
this.data[key] = node.value
})
}
complieText(node) {
let value = node.textContent
let reg = /\{\{(.+?)\}\}/
if (reg.test(value)) {
let k = RegExp.$1.trim()
console.log(k);
node.textContent = value.replace(reg, this.data[k])
new Wathcher(this.data, k, (newValue) => {
node.textContent = newValue
})
}
}
isDirective(attrName) {
return attrName.startsWith("v-")
}
isTextNode(node) {
return node.nodeType === 3
}
isElementNode(node) {
return node.nodeType === 1
}
isAttrNode(node) {
return node.nodeType === 2
}
}
|