总所周知,Vue最大的特点之一就是数据驱动视图,在这里,我们可以把数据理解为状态,而视图就是用户可直观看到页面。页面不可能是一成不变的,它应该是动态变化的,而它的变化也不应该是无迹可寻的,它或者是由用户操作引起的,亦或者是由后端数据变化引起的,不管它是什么引起的,我们统称为它的状态变了,它由前一个状态变到了后一个状态,页面也就应该随之变化,我们把这种特性称之为数据驱动视图。
变化侦测
变化侦测是追踪状态,亦或者说是数据的变化,一旦发生了变化,就去更新视图。
变化侦测可不是个新名词,它在目前的前端三大框架中均有涉及。在Angular中是通过脏值检查流程来实现变化侦测;在React是通过对比虚拟DOM来实现变化侦测,而在Vue中也有自己的一套变化侦测实现机制。
要想知道数据什么时候被读取了或数据什么时候被改写了,其实不难,JS为我们提供了Object.defineProperty方法,通过该方法我们就可以轻松的知道数据什么时候发生变化。
使Object数据变得“可观测”
数据的每次读和写能够被我们看的见,即我们能够知道数据什么时候被读取了或数据什么时候被改写了,我们将其称为数据变的“可观测”。
首先我们定义一个数据对象car:
let car ={
'brand':'BMW',
'price':3000
}
接下来,我们使用Object.defineProperty()改写上面的例子:
let car ={}
let val = 3000
Object.defineProperty(car,'price',{
enumerable: true,
configurable: true,
get(){
console.log('price属性被读取了')
return val
},
set(newVal){
console.log('price属性被修改了')
val = newVal
}
})
通过Object.defineProperty()方法给car定义了一个price属性,并把这个属性的读和写分别使用get() 和set()进行拦截,每当该属性进行读或写操作的时候就会触发get()和set()。
可以看到,car已经可以主动告诉我们它的属性的读写情况了,这也意味着,这个car的数据对象已经是“可观测”的了。
/**
* Observer类会通过递归的方式把一个对象的所有属性都转化成可观测对象
*/
export class Observer {
constructor (value) {
this.value = value
// 给value新增一个__ob__属性,值为该value的Observer实例
// 相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作
def(value,'__ob__',this)
if (Array.isArray(value)) {
// 当value为数组时的逻辑
// ...
} else {
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
/**
* 使一个对象转化成可观测对象
* @param { Object } obj 对象
* @param { String } key 对象的key
* @param { Any } val 对象的某个key的值
*/
function defineReactive (obj,key,val) {
// 如果只传了obj和key,那么val = obj[key]
if (arguments.length === 2) {
val = obj[key]
}
if(typeof val === 'object'){
new Observer(val)
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get(){
console.log(`${key}属性被读取了`);
return val;
},
set(newVal){
if(val === newVal){
return
}
console.log(`${key}属性被修改了`);
val = newVal;
}
})
}
在上面的代码中,我们定义了observer类,它用来将一个正常的object转换成可观测的object。
并且给value新增一个_ob_属性,值为该value的Observer实例。这个操作相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作。
然后判断数据的类型,只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化。最后,在defineReactive中当传入的属性值还是一个object时使用new objectserver(val)来递归子属性,这样我们就可以把obj中的所有属性(包括子属性)都转换成getter/setter的形式来侦测变化。也就是说,只要我们将一个object传到observer中,那么这个object就会变成可观测的、响应式的object。
那么接下来就可以这样定义car:
let car = new Observer({
'brand':'BMW',
'price':3000
})
这样,car的两个属性都变得可观测了。