什么是响应式?
在数据变化的时候,视图也会随着改变,这就是响应式
如何实现响应式?
VUE使用Object.defineProperty 中getter和setter方法中的观察者模式来实现响应式
Object.defineProperty
这个方法是对象方法,在一个对象上定义一些新的属性及方法,或改变对象的现有方法,并返回这个对象。
举个示例看一下:
var mVal = 0;
var o = {};
Object.defineProperty(o, 'm'){
get: function(){
console.log('这里监听获取m值");
return mVal;
},
set: function(newVal){
console.log('这里监听修改m值");
mVal = newVal;
},
enumerable : true,
configurable : true
}
o.m = 88;
console.log(o.m); // 88
分析:当调用o.m给对象o中的m属性赋值的时候,会调用set方法,将m的值赋给mVal,此时就会调用get方法,获取这个值。
通过这种方式我们就可以实现一个简单的vue双向绑定了,给data中的所有属性加上get和set方法。
观察者模式
什么是观察者模式?
观察者模式分为注册环节与发布环节。
将需要修改的属性集中注册一下,当处理完后一起发布出去。
function Observer(){
this.dep = [];
register(fn){
this.dep.push(fn)
}
notify(){
this.dep.forEach(item => item())
}
}
依次注册多个想要执行的函数
const wantCake = new Observer();
wantCake.register(console.log('call dish'));
wantCake.register(console.log('call jaks'));
wantCake.register(console.log('call mejdh'));
在完成后通知所有客户并执行函数
wantCake.notify()
原理解析
首先参考一张原理图
我们分3个步骤来解释
1、init阶段,VUE实例的data属性reactive化,加上get、set方法
function defineReactive(obj: Object, key: String, ...){
const dep = new Dep();
Object.defineProperty(o, key){
get: function reactiveGetter(){
...
dep.depend();
return value;
...
},
set: function reactiveSetter(newVal){
...
val = newVal;
dep.notify();
...
},
enumerable : true,
configurable : true
}
}
const Dep{
static target: ?Watcher;
subs: Array<Watcher>;
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
这里的dep是一个观察者类,每一个属性都有一个dep,调用getter时去dep里注册函数,在调用setter时,执行注册的函数。
2、mount阶段
mountComponent(vm: Component, el: ?Element, ...) {
vm.$el = el
...
updateComponent = () => {
vm._update(vm._render(), ...)
}
new Watcher(vm, updateComponent, ...)
...
}
class Watcher {
getter: Function;
// 代码经过简化
constructor(vm: Component, expOrFn: string | Function, ...) {
...
this.getter = expOrFn
Dep.target = this // 注意这里将当前的Watcher赋值给了Dep.target
this.value = this.getter.call(vm, vm) // 调用组件的更新函数
...
}
}
在mount时会创建一个Watcher类,这个类是链接dep与vue组件的桥梁。每一个watcher对应一个vue component。
在new Watcher()时会调用组件的getter方法,此时会调用render重新渲染函数。
render函数会访问data属性,这时就去调用这个属性的getter函数
// getter函数
get: function reactiveGetter () {
....
dep.depend()
return value
....
},
// dep的depend函数
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
在depend函数中,dep就是watcher对象本身。这样每次渲染这个组件时,如果用到了这个属性,组件对应的watcher都会注册到这个属性的dep中。这个过程被称为依赖收集。
在收集完所有的依赖后,如果这个属性变化,就会通知watcher去更新相关的组件。
3、更新阶段
属性改变时,回去调用dep里面的notify函数,然后通知所有的watcher去调用update函数进行更新。
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
4、流程展示
reactive属性
setter
Dep
notify
watcher
re-render
VueComponent
5、总结一下
第一步:组件初始化时给data中所有属性添加get、set,reactive化;然后注册一个Watcher对象,此时watcher会立即调用组件里的render去生成虚拟DOM,此时会用到data,所以会触发getter函数,将当前的watcher注册到sub里。
第二步:在data属性变化时,会遍历sub中所有watcher对象,通知它们去渲染组件。
借鉴文章:https://zhuanlan.zhihu.com/p/88648401