vue作为一个MVVM框架,是如果对数据属性实现响应式的呢?通过深入研究,发现它是通过Object.defineProperty(只支持纯对象)绑定get,set来实现的,下面就来探究一下其中的原理。
Object.definePropety()
/**
* @param {[Object]} obj 目标对象
* @param {[String]} prop 目标对象的属性
* @param {[String]} descriptor [属性描述符:数据描述符和存取描述符两种形式]
*/
var obj = {};
var descriptor = Object.create(null) // 没有继承的属性
// 默认没有enumerable,没有configurabel,没有writeable
descriptor.value = 'static';
object.definePorperty(obj, 'key', descriptor);
// 属性描述符
object.definePorperty(obj, 'key', {
enumerable: false, // 定义对象的属性是否是可枚举
writeable: faslse, // 定义对象的属性是否可修改
configurable: fase, // 定义对象属性是否可删除
value: "static"
})
// 存取描述符
var bValue
object.defineProperty(obj, 'key', {
get: function() {
return bValue;
},
set: function(newValue) {
bValue = newValue
},
eumerable: true,
configurable: true
})
vue在denfineProperty的方法上进行进一步的封装
function defineReactive(data,key,value) {
Object.defineProerty(data,key,{
get: function() {
return value
},
set: function(newValue) {
if(newValue === value) return;
value = newValue;
},
eumerable: true,
writeable: true
})
}
怎么观察Observer
我们之所以要观察Observer,是为了当数据属性发生变化的时候,通知那些使用到该属性的地方。
这要我们就可以先把所有使用到该属性依赖全部收集起来,当属性改变的时候,循环通知所有的属性依赖进行更新
依赖搜集
在defineReative的基础上进一步实现
/**
* [defineReactive description]
* @param {[Object]} data [对象obj]
* @param {[String]} key [对象属性]
* @param {[String]} value [对象属性的值]
* @return {[type]} [description]
*/
function defineReactive(data,key,value) {
let dep = [];
Object.defineProperty(data,key,{
eumerable: true,
configurable: true,
get: function() {
dep.push(watcher);
},
set: function(newValue) {
if(value === newValue) return;
for(let i = 0; i < dep.length; i++ ) {
watcher.update(newValue, value);
}
value = newValue
}
})
}
// 进一步把收集依赖的部分封装起来
class Dep {
static target: ?Watcher;
id: Number;
subs: String<watcher>;
construstor() {
this.id = uid++;
this.subs = [];
}
addSub(sub: Watcher) {
this.subs.push(sub)
}
removeSub(sub: Watcher) {
remove(this.subs, sub)
}
depend() {
if(Dep.target == Watcher) {
this.addSub(Dep.target)
}
}
notify() {
const subs = this.subs.splice();
for(let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
// 修改后的代码
function defineReactive(data,key,value) {
let dep = New Dep();
Object.defineProperty(data,key, {
eumerable: true,
configaruable: true,
get: function() {
dep.depend();
return value;
},
set: function(NewValue) {
if(value === NewValue) return;
dep.notify();
value = NewValue;
}
})
}
总结: 要观察就要收集依赖到Dep用于专门存储依赖, 上面我们将所有存储到dep数组的叫做watcher。致这些watcher依赖是在vue中的templete,watch,computed中都要是收集的,当数据改变的时候就去通知到这些地方,这样就需要抽象出一个能够处理不同情况的类,然后我们在依赖收集的时候只需要收集这些封装好的类进来,通知也是通知到它一个,然后它负责通知其他地方使用到该属性的地方,这个类我们叫它watcher
watcher
class Watcher {
constructor(expOrFn, callback) {
this.getter = parsePath(expOrFn);
this.callback = callback;
this.value = this.get();
}
get() { // 执行get, 将自己注入到dep.target
Dep.target = this;
this.getter.call(vm); // 触发getter,getter里面触发depend, depend执行addSub,将watcher放到dep依赖中
Dep.tabget = undefined;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.callback(this.value, oldValue)
}
}
上面的代码就是一个完整的一个watcher,所做的事情就是获取和更新要用到的属性
至此,单个属性监测响应的整个过程就实现了,对于多个属性的监测代码实现如下,遍历对象的所有属性
function observe(obj) {
if(obj.constructor !== Object) return;
const keys = obj.keys();
for(let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
function defineReactive(data,key,value) {
observe(val);
let dep = new Dep();
Object.defineProperty(data,key, {
eumerable: true,
configurable: true,
get: function() {
dep.depend();
return value;
},
set: function(newValue) {
if(value === newValue) return;
dep.notify();
value = newValue;
}
})
}
说明
- 属性的可枚举性: 可以通过for-in循环来进行遍历
- Object.keys() : 该方法会返回一个由一个给定对象的自身可枚举属性组成的数组