前言
笔者理解的Vue2.x的核心原理:
- 基于数据劫持和订阅发布模式实现数据绑定(M ==> V)
- 虚拟dom(本文不涉及)
MVVM模式
MVVM模式实现了Model到View的数据绑定,让开发者只用关心Model的变化。而传统的MVC模式中的Controller需要开发者自己实现根据Model变化引起的View变化(jquery只解决了dom操作的复杂度)。
下表是三大前端框架对于数据绑定的实现
| 三大前端框架 | 实现方式 |
|---|---|
| React | setState+虚拟dom |
| Vue | 数据劫持+虚拟dom |
| Angular | 特定事件的脏值检查 |
下图是Vue实现数据绑定的逻辑图
简易Vue实现
Vue初始化分为数据劫持和模版编译两部分
数据劫持中会遍历data属性,每次遍历中会实例化一个新的依赖收集者(订阅者)类,也就是说每个属性对应一个dep。get方法中会收集监听者Watcher实例。set方法会通知dep实例执行更新
代码只是简单实现了文本节点的数据绑定,未实现元素节点的指令解析。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue</title>
</head>
<body>
<div id="root">
<p>{{name}}</p>
<p>{{age}}</p>
</div>
</body>
document.addEventListener('DOMContentLoaded', function() {
const app = new Vue({
el: '#root',
data: {
name: 'Neil',
age: 18
}
})
setTimeout(() => {
app.$data.age = 20;
}, 1000)
})
// Vue类
class Vue{
constructor(options) {
this.$el = document.querySelector(options.el);
this.$data = options.data;
// 数据劫持
this.observe(this.$data);
// 模版编译
this.compile(this.$el)
}
// 数据劫持
observe(data) {
Object.keys(data).map(key => {
// 创建Dep实例
const dep = new Dep();
data['_' + key] = data[key];
// 利用闭包在每一个属性中保存dep
Object.defineProperty(data, key, {
get() {
// 初始化编译时,会将使用了该属性的文本节点赋值给dep.target,并添加到订阅数组。
Dep.target && dep.addSubNode(Dep.target);
return data['_' + key]
},
set(newValue) {
if (newValue === data['_' + key]) {
return
}
data['_' + key] = newValue;
dep.notify();
}
})
})
}
// 模版编译
compile(el) {
const nodeList = Array.from(el.childNodes);
nodeList.forEach(node => {
if (node.nodeType === 1) {
console.log('处理元素节点');
// 递归解析
this.compile(node);
} else if (node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)) {
console.log('处理文本节点');
// 初始化文本节点
node.textContent = this.$data[RegExp.$1];
// 依赖收集
new Watcher(this, RegExp.$1, function(newValue) {
node.textContent = newValue;
});
}
})
}
}
// 依赖收集类(订阅者,订阅属性变化)
class Dep {
constructor() {
this.subNodeList = []
}
addSubNode(subNode) {
this.subNodeList.push(subNode)
}
notify() {
this.subNodeList.map(subNode => {
subNode.update()
})
}
}
// 监听者类(new构造后自动被添加到对应属性的订阅队列)
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
// 将当前Watcher对象添加到订阅队列
Dep.target = this;
this.vm.$data[key];
Dep.target = null;
}
update() {
const newVal = this.vm.$data[this.key];
this.callback(newVal);
}
}
</script>
</html>