使用数据劫持配合观察者模式、发布订阅模式运行响应式
/**
* 仿写一个简单的Vue.js
* Vue使用层上是数据驱动,而源码上数据更新了,dom也要更新
*/
class Vue {
//构造器
constructor(options) {
this.$options = options;
//观察者监听事件
this.$watchEvent = {};
//观察者 模板更新
this.observer();
//生命周期函数created
if (Object.prototype.toString.call(options.created) === "[object Function]") {
options.created.call(this);
}
};
/**
* 编译
* 对模板变量
*/
compile(node) {
node.childNodes.forEach((item, index) => {
//元素节点
if (item.nodeType === 1) {
//事件监听
if (item.hasAttribute("@click")) {
let vmKey = item.getAttribute("@click");
//做函数监听
if (Object.prototype.toString.call(this.$options.methods[vmKey]) === "[object Function]") {
item.addEventListener('click', (event) => {
this.$options.methods[vmKey].call(this, event);
});
}
}
//递归
this.compile(item);
}
//文本节点
if (item.nodeType === 3) {
//正则
let reg = /\{\{(.*?)\}\}/g;
//textContent属性表示一个节点及其后代的文本内容。
let text = item.textContent;
//编写规则match匹配字符 vmkey是匹配到的data属性
item.textContent = text.replace(reg, (match, vmKey) => {
//去掉两边空格
vmKey = vmKey.trim();
//给模板匹配到的每一个模板变量创建监听对象
let watch = new Watch(this, vmKey, item, "textContent")
//判断有没有这个属性,没有则不需要进行监听
if (this.hasOwnProperty(vmKey)) {
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watch);
} else {
//初始化监听
//由于一个data可能在模板上使用多次,那么应该使用一个数组存储
this.$watchEvent[vmKey] = [];
this.$watchEvent[vmKey].push(watch);
}
}
//替换模板
return this.$data[vmKey]
})
}
})
};
/**
* 观察者
* 对this.$data的变化进行监听,更新模板
*/
observer() {
for (let key in this.$data) {
let value = this.$data[key];
let that = this;
Object.defineProperty(this.$data, key, {
get() {
return value;
},
set(newVal) {
value = newVal;
//判断是否有这个属性的监听对象,意味着这个data属性是否在模板中被使用了
if (that.$watchEvent[key]) {
//其value是一个数组,因为data属性可能在模板中使用了不止一次,这是在compile的使用push的
for (let val of that.$watchEvent[key]) {
val.update();
}
}
}
})
}
}
}
/**
* 监听对象
*/
class Watch {
constructor(vm, key, node, attr) {
//vue实例对象
this.vm = vm;
//属性名称
this.key = key;
//节点
this.node = node;
//节点属性如textContent
this.attr = attr;
};
//更新模板
update() {
//将最新的vm data给到节点属性
this.node[this.attr] = this.vm[this.key];
}
}