变化侦测
前言
众所周知vue最大的特点之一是数据驱动视图,举例一个公式:
UI = render(state)
上方公式state(状态)是输入,UI(视图)是输出,render(渲染)是驱动state来改变UI的方法,当state发生变化时,输出也会跟着变化,我们把这种特性称之为数据驱动视图
我们知道state和UI是用户规定的,而不变的是render(),所以VUE扮演的就是render()这个角色,VUE发现了state的变化,经过一系列的加工,最终反映在UI上,那么VUE是如何知道state的变化的呢,由此引出了变化侦测
结论: 数据驱动视图就是状态变化引起了视图变化
变化侦测
变化侦测就是追踪状态的变化,一旦发生了改变就去更新视图,js为我们提供了Object.defineProperty方法,通过该方法,我们可以知道数据在什么时候发生了变化
Object的变化侦测
使Object的数据变得可观测
数据的每次读和写都能被我们看到,即我们知道数据什么时候读取或者数据什么时候被改写,就是数据变得可观测。
要将数据变得可观测,我们就要借助Object.defineproperty()方法。
首先我们定义一个数据对象car
let car = {
'brand': 'BMW',
'price': 3000
}
要如何做才能让car变得可观测呢,我们使用Object.defineproperty()来改写car
let car = {}
let val = 3000
Object.defineproperty(car,'price',{
enumerable: true, //是否可以被枚举
configurable: true, //是否可以被配置
get(){
console.log("price属性被读取了")
return val
},
set(newVal){
cosole.log("price属性被改写了")
return newVal
}
})
通过Object.defineproperty()方法给car定义一个price属性,并把这个属性的get()和set()进行拦截,每当该属性进行读或者写操作的时候就会触发get()和set(),这就已经把car的属性变成可观测的了
为了把car的所有属性都变成可观测的,我们可以编写如下代码
//源码位置: src/core/observer/index.js
/**
* observer类会通过递归的方法把一个对象的所有属性都转化成可观测对象
*/
export class Observer {
constructor(value){ //Observer的构造函数, this指向实例
this.value = value
//给value新增一个__ob__属性,值为该value的Observer实例
//相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作
def(value,'__ob__',this)
if(Array.isArray(value)){
//当value为数组时的逻辑
//...
}else{
this.walk(value)
}
}
walk(obj: Object){
//如果时对象就将对象的key枚举出来进行循环
//让每一个对象的属性都执行一遍可观测的方法
const keys = Object.keys(obj)
fot(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的形式来侦测变化,最后在defineReactiver中传入的属性值还是一个object时使用new observer(val)来递归子属性,这样我们就可以把obj中的所有属性(包括子属性)都转换成getter/setter的形式来侦测变化,只要我们传一个object到observer中,那么这个object就会变成可侦测的,响应式的obj
现在我们可以这样定义car:
let car = new Observer({
'brand':'BMW',
'price': 3000
})