vue的双向数据绑定(响应式)原理到底是什么呢?这还是得从vue的源码说起:
我们知道vue的一大特点是数据驱动视图
,如何理解数据驱动视图这六个字呢?
数据:state;
驱动:render函数;
视图:页面的UI;
这样说来,其实我们可以得到这样一个公式:UI = render(state),state的变化直接导致UI的变化,而始终不变的是render,vue就是扮演了render的角色。那么vue是如何知道state变化的呢?有个词叫变化侦测
,因为在当前的主流技术栈,vue,react,angular中都有提到,也就是状态追宗
,一旦数据变化,就去更新视图。我们先从对象的变化侦测说起。
所谓变化侦测,就是我们能够知道数据什么时候被读取了或数据什么时候被改写了。
数据的侦测
1、实现对象的侦测(可观察)
对象的侦测是借助Object.defineProperty()
这个方法。
首先定义一个对象animal:
let animal = {
name: 'dog',
age: 3
那么这个animal的对象的属性被修改的时候,我们是如何知道的呢,且看下面的操作:
let name = 'cat'
Object.defineProperty(animal, 'name', {
configrable: true, // 描述属性是否配置,以及可否删除
enumerable: true, // 描述属性是否会出现在for in 或者 Object.keys()的遍历中
writable: true, // 属性的值是否可以被重写
get () {
// 这里读取了name的值
return name
},
set (value) {
// 这里设置了name的值
name = value
}
这样一来,当name的值被改成cat的时候,就会变的可观测,可被侦测。但是这只是做到了对象的某一个属性被侦测,但是对象的属性往往不是一个,多个的时候又该怎么办呢?其实也很简单,对,没错,就是你想说的递归。直接上代码:
// 源码位置:src/core/observer/index.js
export class Observer {
constructor (value) {
this.value = 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])
}
}
}
function defineReactive (obj,key,val) {
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类,用于将对象的所有属性都转变为可侦测,并且,我们要给每一个可侦测的对象添加一个__ob__属性,值为value的Observer的实例,这就相当于给每一个value设置了标识,表示这个value是响应式的了。那么当value是一个对象的时候,我们拿到他的所有的key,循环遍历调用Object.defineProperty()的方法,调用get/set方法来侦测,当发现传入的是一个Object的时候,