前言
- 最近看了一下vue的源码,记录了一下自己对于vue2.0响应式原理的理解,如果有什么错误的地方,希望大佬能够指点出来。
- 大家都知道vue的响应式原理是通过Object.defineProperty来实现的,但这种回来显然抽象了很多,如果问一下更细致的部分,可能就有点不知道了,当你把这篇文章弄懂之后,就能够细说一二了,话不多说,上正文。
先上代码
<div id="app">
<div>
<p>{{email}}</p>
<p>{{name}}</p>
<p>{{tel}}</p>
<p>{{age}}</p>
</div>
</div>
class Vue {
constructor(options) {
this.$options = options;
this.$el = el;
this.$data = options.data || {};
this.doomPool = {} //放置绑定了data的dom元素
this._definePropety(this.$data); //将data绑定到vue上
new Observe(this.$data);将data转化为响应式数据
new Compiler(this); //模板解析,将{{data}}转化为正常值
}
_definePropety(data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key];
},
set(newValue) {
if (newValue === data[key]) {
return;
}
data[key] = newValue;
},
});
});
}
}
//给数据添加get set属性
class Observe {
constructor(data) {
this.walk(data);
}
walk(data) {
//判断data是不是对象
if (typeof data !== "object" || !data) {
return;
}
//遍历data
Object.keys(data).forEach((key) => {
this.definedReactive(data, key, data[key]);
});
}
definedReactive(obj, key, value) {
let that = this;
//负责收集依赖(观察者), 并发送通知
let dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
//收集依赖 Dep.target就是watcher
Dep.target && dep.addSubs(Dep.target);
return value;
},
set(newValue) {
if (newValue === value) {
return;
}
value = newValue;
//数据变换 ,发送通知给watcher的update ,在渲染视图里的数据
dep.notify();
},
});
}
}
//依赖
class Dep {
constructor() {
//收集观察者
this.subs = [];
}
//添加观察者
addSubs(watcher) {
if (watcher && watcher.update) {
this.subs.push(watcher);
}
}
//数据变换,就调watcher的update方法
notify() {
this.subs.forEach((watcher) => {
watcher.update();
});
}
}
//观察者
class Watcher {
constructor(vm, key, callback) {
// 向Dep的静态属性上添加watcher
Dep.target = this;
const self = this;
this.vm = vm;
this.callback = callback;
this.key = key
this.oldValue = this.vm[key]
Dep.target = null;
}
//当数据变化的时候,更新视图
update() {
let newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
this.callback(newValue, this.node)
}
}
//模板渲染
class Compiler {
constructor(vm) {
//传vue实例
this.el = vm.$el;
this.vm = vm;
this.compile(this.el);
}
compile(el) {
const child = el.childNodes;
child.forEach((item) => {
if (item.nodeType === 3) {
const _value = item.nodeValue;
if (_value.trim().length) {
let reg_isValid = /\{\{.*?}\}/.test(_value);
if (reg_isValid) {
const _key = _value.match(/\{\{(.*?)}\}/)[1].trim();
this.vm.doomPool[_key] = item.parentNode; //将dom节点保存到dom池中
item.parentNode.innerText = this.vm[_key] || undefined; //这里会触发收集依赖
new Watcher(this.vm, _key, (newValue) => {
this.vm.doomPool[_key].innerText = newValue;
});
}
}
}
item.childNodes && this.compile(item);
});
}
}
var vue = New Vue({
el: "#app",
data: {
name: "123",
email: "456",
age: "789",
tel: "123132",
},
})
这里我们就简单的实现了一个vue的响应式,接下来我们就重点讲一下watcher,dep和observer的关系。
内容讲解
- 我们通过_definePropety函数将data中的值绑定到vue上面,
- 我们将data中的值通过Observe函数添加get和set属性。
- 我们通过Compiler函数对整个dom元素进行遍历,然后解析遇到的{{}},然后给它赋值,并保存要改变的dom元素。
- 通过addSubs给要渲染的值添加一个watcher,与渲染无关的值并不会在依赖收集器中添加到监听。当我们改变data中的值的时候,如果是渲染的值那么就会先执行deep中的notify函数来对整个subs进行遍历,通知所有Watcher实例更新。
- 总之observer的作用就是给让数据变成响应式数据,然后dep就是存放watcher和对watcher的管理,当数据变动时就通知存放的所有watcher进行更新,watcher就负责更新视图。