Vue的一大特点是数据响应式,数据的变化会作用于UI而不用进行DOM操作。原理上来讲是利用了js的defineProperty(obj,key,{})的特性。通过定义对象属性setter方法拦截对象属性变更,从而通过Watcher通知视图更新。在Watcher中会调用updateComponent,而updateComponent方法中会调用render.js中的_render()方法和_update()方法,这样视图UI也就进行了更新(vdom转化为了真实dom)
defineReactive()方法
这个方法中用到了js的defineProperty属性,大家都知道Vue的响应式是通过defineProperty实现的,只要是被defineProperty改造过的属性就具备了**【响应式】**的,原因是改变这个对象的时候会触发getter和setter方法,通过这两个方法就可以知道什么时候数据变化了,从而就可以在这个时候通知watcher中的render方法进行视图更新了。代码如下:
// 对象响应式
function defineReactive(obj, key, val) {
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get:传入的'+key+'这个属性被获取了,这里可以做些什么呢?' + key);
return val
},
set(newVal) {
if (newVal !== val) {
console.log('set :' + key + '属性被改变了,我要把新值放到界面上' + newVal);
val = newVal
}
}
})
}
let data = {msg:'我是一条数据'}
defineProperty(data,msg,data.msg)
console.log(data.msg) //传入的msg这个属性被获取了,这里可以做些什么呢?
//set
data.msg = '修改这个数据' //console.log输出 :set :msg属性被改变了,我要把新值放到界面上
Observer
通过上面的代码就可以实现对date的属性进行监听,Vue中通过添加一个Observer来监听数据的变化,这称为观察者。
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
// 希望传入的是obj
return
}
new Observer(obj)
}
class Observer {
constructor(value) {
this.value = value
if (typeof value === 'object') {
this.walk(value)
}
}
walk(obj) {
//对象数据响应化
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
});
//数组数据响应化
if (Array.isArray(obj)) {
//覆盖原型,替换7个数组操作方法
obj._pro_ = arrayProto
//对数组元素执行响应化
for (var i = 0; i < obj.length; i++) {
observe(obj[i])
}
}
}
}
Dep (依赖收集)
有了监听数据变化的Observer,那怎么把这些变化都收集起来呢,因为vue是把所有的变化都收集起来,一次进行更新的,不然变化一次就更新一次,这效率也太低了,再说了也太消耗性能。这样就有了Dep来收集依赖。这些依赖就是用来管理data中所有属性变化的。
//依赖收集:管理某个key相关的所有Watcher(目前这里我用的还是vue1的实现,作一下简单说明,所以还是每个key对应一个Watcher,vue2.0就是一个组件对应一个Watcher了)
class Dep {
constructor() {
this.deps = []
}
addDep(dep) {
this.deps.push(dep)
}
notify() {
this.deps.forEach(dep => dep.update())
}
}
怎么进行依赖收集的呢?
顺便把上面的defineReactive()方法改一下:
// 对象响应式
function defineReactive(obj, key, val) {
// 递归,如果传入的val是还是一个对象,再执行observe(val),比如:{foo:'foo',bar:'bar',baz:{a:1}}中的baz:{a:1}
observe(val)
//创建一个Dep和当前key一一对应
const dep = new Dep()
// 对传入obj进行访问拦截
Object.defineProperty(obj, key, {
get() {
console.log('get ' + key);
//依赖收集在这里
console.log('Dep:===', dep)
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if (newVal !== val) {
console.log('set ' + key + ':' + newVal);
// 如果传入的newVal依然是obj,需要做响应化处理,如obj.baz.a = 10000
observe(newVal)
val = newVal
/* //粗糙的看下效果
watchers.forEach(w => w.update()) */
//通知更新
dep.notify()
}
}
})
}
Watcher
通过上面的Observer和Dep已经可以对数据的变化作出监听并把这些依赖都收集起来了,但是怎么通知视图进行更新呢?所以就出现了Watcher这个构造函数。Watcher就是在数据变化时通知render和update去更新视图的。
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm
this.key = key
this.updateFn = updateFn
// watchers.push(this)
//Dep.target静态属性上设置为当前Watcher实例
Dep.target = this
this.vm[this.key] //读取触发了getter
Dep.target = null //收集完成就置空
}
update() {
this.updateFn.call(this.vm, this.vm[this.key])
}
}
总结
