一句话概括
基于 ES5 的 Object.defineProperty() 这个方法对数据做劫持。劫持数据的 setter 与 getter 。然后结合
发布订阅模式在数据发生数据,通知页面进行更新。实现时通过三个类:Vue类、Compile(解析双花括号的类)、发布订阅类(Event)
由于 ES5 的 Object.defineProperty() 这个方法不兼容 IE8 。所以我们的 vue 的兼容性也是不兼容 IE8 既以下版本
核心:
// 取值: this.name 的时候,会进入到 get 函数中
// 赋值: this.name = xxxx 的时候,会进入到 set 函数中
Object.defineProperty(this, 'name', {
get () {
console.log('取值')
return '我的天'
},
set (value) {
console.log('赋值', value)
}
})
具体代码实现:
//模拟vue创建一个vue类
class Vue {
constructor(option) {
//option : 实例化时传入的全部参数。如下例子则是{el、data···}
/**
* const vm = new Vue({
el:"#app",
data:{
name:"张三",
age:16,
sex:"男"
}
})*/
//实现vue的实例属性 => vm.$el、vm.$el
this.$el = document.querySelector(option.el);
this.$data = option.data;
this.observe(this.$data);
//把监听对象挂载到vue实例中的_el上。然后在下面newCompile里把vue实例传下去,则在compile中能通过this.vm._el.$on监听事件
this._el = new Event();
new Compile(this.$el, this);
}
//这个方法是实现Vue实例对象中的data属性里的每个对象属性直接挂到实例上(实现Vue代理属性)即
// this.name === this.$data.name
// this.age === this.$data.age
//Object.defineProperty()也是实现双向数据绑定的重要原理
observe(data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
get() {
//当vm.name 访问时会进入这个方法
return data[key];
},
set(value) {
//vm.name = "李四" 进行设置值时会进入这个set方法
data[key] = value;
//当修改实例属性的时候发布消息。通知修改页面{{}}的值
this._el.$emit(key);
},
});
});
}
}
//解析{{}}的类,这个类需要两个参数,一个是Vue实例对象(让能知道实例中的数据改变了),一个是dom元素el(能够在知道vm的数据改变了之后,操作el,修改dom中的值)
class Compile {
constructor(el, vm) {
//把vue实例也挂载在Compile实例上。因为获取这个实例上的值来改变dom的值
this.vm = vm;
this.compile(el);
}
compile(el) {
// 遍历 el 这个 DOM 对象 子节点
el.childNodes.forEach((node) => {
// 得到所有的文本节点
// if (node.nodeType === 3) {
// console.log(node.textContent)
// }
// 得到所有的文本节点,节点的文本内容类似 {{ xxx }} 这种格式的
if (node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)) {
//取出dome{{}}中的表达式。$1则是能达到(.*)中的表达式。是正则的一个属性
const exp = RegExp.$1.trim();
//将页面的{{name}}中的name赋值为vm中的name属性的值
node.textContent = this.vm[exp];
//this.vm === new Vue。订阅exp(data中的key)这个事件,当exp发布消息时,执行回调,改变页面{{}}号里面的内容
this.vm._el.$on(exp, () => {
node.textContent = this.vm[exp];
});
}
//递归。取出el中的所有文本节点
if (node.childNodes) {
this.compile(node);
}
});
}
}
//发布订阅类,实时监听。在实例数据改变的时候发布消息,告知compile类,改变页面dom的数据
class Event {
constructor() {
this.dep = {};
}
$on(evetName, callBack) {
if (!this.dep[evetName]) {
this.dep[evetName] = [];
}
this.dep[evetName].push(callBack);
}
$emit(evetName) {
if (this.dep[evetName]) {
this.dep[evetName].forEach((cb) => {
cb();
});
}
}
}
页面结果
<body>
<div id="app">
<p>{{name}}</p>
<p>{{age}}</p>
<p>{{sex}}</p>
</div>
<script src="./index1.js"></script>
<script>
const vm = new Vue({
el:"#app",
data:{
name:"张三",
age:16,
sex:"男"
}
})
</script>
</body>
实例化后页面显示:
然后改变实例vm的name和age属性都会引起页面的变化