Vue的MVVM的实现主要是应用了观察者模式和脏值检查。可以借鉴这张图
图中的含义:
首先自定义一个Vue类(这里取名为Mvue):
/**
* 入口类
*/
class Mvue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
//1.实现一个数据观察者
new Observer(this.$data);
//2.实现一个指令解析器
new Compile(this.$el, this);
this.ProxyData(this.$data);
}
}
ProxyData(data) {
for (const key in data) {
Object.defineProperty(this, key, { value: data[key] });
}
}
}
然后再实例化一个Observer对象进行属性拦截,设置get()和set()方法,并在Compile对象进行模板编译赋值的时候在Dep(依赖器类)中添加Watcher(观察者类)的引用
Observer类:
/**
* @description 属性拦截
*/
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(value);
const dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
Dep.target && dep.addSub(Dep.target);
return value;
},
set: newVal => {
debugger;
this.observer(newVal);
if (newVal != value) {
value = newVal;
//告知dep通知变化
dep.notify();
}
}
});
}
}
Dep(依赖器类)类:
/**
* @description 依赖器 添加所有watcher
*/
class Dep {
constructor() {
this.watchers = new Array();
}
/**
* @param {object} watch 观察者对象
*/
addSub(watcher) {
this.watchers.push(watcher);
}
/**
* 通知更新
*/
notify() {
for (const item of this.watchers) {
item.update();
}
}
}
Watcher(观察者)类:
class Watcher {
constructor(vm, expr, callback) {
this.vm = vm;
this.expr = expr;
this.cb = callback;
this.oldVal = this.getOldVal();
}
/**
*
* @param {object} callback 回调更新函数
*/
update() {
console.log(this.expr);
const newVal = compileUtil.getValue(this.expr, this.vm.$data);
if (newVal !== this.oldVal) {
this.cb(newVal);
}
}
getOldVal() {
Dep.target = this;
console.log(this.expr);
const oldVal = compileUtil.getValue(this.expr, this.vm.$data);
Dep.target = null;
return oldVal;
}
}
Compile(编译器)类
const compileUtil = {
/**
* 编译text
* @param {node} node
* @param {string} value
* @param {object} vm
*/
text(node, value, vm) {
let attributevalue;
if (value.indexOf('{{') !== -1) {
value.replace(/\{\{(.+?)\}\}/g, (...arg) => {
new Watcher(vm, arg[1], newValue => {
this.updater.textUpdate(node, newValue);
});
attributevalue = this.getValue(arg[1], vm.$data);
});
} else {
attributevalue = this.getValue(value, vm.$data);
new Watcher(vm, attributevalue, newValue => {
debugger;
this.updater.textUpdate(node, newValue);
});
}
this.updater.textUpdate(node, attributevalue);
},
html(node, value, vm) {
const attributevalue = this.getValue(value, vm.$data);
new Watcher(vm, value, newValue => {
this.updater.htmlUpdate(node, newValue);
});
this.updater.htmlUpdate(node, attributevalue);
},
model(node, value, vm) {
const attributevalue = this.getValue(value, vm.$data);
new Watcher(vm, value, newValue => {
this.updater.modelUpdate(node, newValue);
});
this.updater.modelUpdate(node, attributevalue);
//添加input事件
node.addEventListener('input', e => {
this.setValue(value, vm.$data, node.value);
});
},
on(node, value, vm, eventName) {
const fn = vm.$options.methods && vm.$options.methods[value];
node.addEventListener(eventName, fn.bind(vm), false);
},
setValue(value, $data, newVal) {
value.split('.').reduce((data, currentValue) => {
debugger;
data[currentValue] = newVal;
}, $data);
},
/**
* 获取值
* @param {string} data
*/
getValue(value, data) {
return value.split('.').reduce((data, currentValue) => {
return data[currentValue];
}, data);
},
updater: {
modelUpdate(node, value) {
node.value = value;
},
textUpdate(node, value) {
node.textContent = value;
},
htmlUpdate(node, value) {
node.innerHTML = value;
}
}
};
class Compile {
constructor(el, vm) {
this.vm = vm;
//获得挂载节点
this.el = this.IsElementNode(el) ? el : document.querySelector(el);
//获得文档碎片
const nodeFragement = this.GetFragementDocument(this.el);
//划分虚拟文档
this.compile(nodeFragement.childNodes);
this.el.append(nodeFragement);
}
/**
*划分节点
* @param {NodeList} node
*/
compile(node) {
for (let nodeItem of [...node]) {
if (this.IsElementNode(nodeItem)) {
this.compileElement(nodeItem);
} else {
this.compileText(nodeItem);
}
if (nodeItem.childNodes && nodeItem.childNodes.length > 0) this.compile(nodeItem.childNodes);
}
}
compileText(node) {
const content = node.textContent;
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtil['text'](node, content, this.vm);
}
}
compileElement(node) {
[...node.attributes].forEach((attr, index) => {
const { name, value } = attr;
if (this.isDirect(name)) {
//获得指令名称text,html,model...
const [, directName] = name.split('-');
//解析事件指令 v-on:click:
const [dirName, eventName] = directName.split(':');
compileUtil[dirName](node, value, this.vm, eventName);
} else if (this.isEvent(name)) {
const [, eventName] = name.split('@');
compileUtil['on'](node, value, this.vm, eventName);
}
node.removeAttribute(name);
});
}
/**
* 判断是否是指令
*/
isDirect(nodeName) {
return nodeName.startsWith('v');
}
isEvent(nodeName) {
return nodeName.startsWith('@');
}
IsElementNode(node) {
return node.nodeType === 1;
}
GetFragementDocument(node) {
const documentFragement = document.createDocumentFragment();
let firstChildNode = '';
while ((firstChildNode = node.firstChild)) {
documentFragement.append(firstChildNode);
}
return documentFragement;
}
}
很多小伙伴到这里可能有疑惑,Dep在什么时候添加了对Watcher的引用,其实是在第一次初始化的调用CompileUtil 对象中的模板解析方法的时候添加的,当实例化一个Watcher的同时,调用compileUtil中的getValue()方法取出oldValue的值时,调用属性的get()方法添加了对Watcher的引用。当属性发生改变时,使用set()方法调用Dep的notify方法遍历watcher对象并调用update()方法,更新视图。