Vue 的响应式原理好像挺神奇的,但是仔细观察,不用看源码也能明白其中的原理。
一、Vue 对数据做了什么?
首先我们来做一个小实验,当我们正常打印一个含有一个属性为 n 的对象时,类如 {n:1},可以看到控制台的打印结果是:
现在我们将这个对象放在 Vue 构造选项的 data 内,代码如下所示:
const myData = {
n:1
}
const vm = new Vue({
data:{
myData
},
render: h => h(App)
}).$mount("#app");
console.log(vm.myData)
可以看到控制台的打印结果是:
可以发现,一个简简单单的对象经过 Vue 之后就变得比较复杂了,多了对原来属性 n 的 getter 和 setter,那么 Vue 又是怎样实现数据响应式的呢?先从 getter 和 setter 讲起。
二、getter 和 setter
getter 和 setter 是 ES6 新增的语法,按照官方文档说,get语法将对象属性绑定到查询该属性时将被调用的函数,当尝试设置属性时,set语法将对象属性绑定到要调用的函数。简单点,当你读取一个对象属性时就会调用 get 函数,修改时调用 set 函数,调用这两个函数不需要像普通函数那样加圆括号实现调用,如下所示:
const obj = {
_n:1,
get n (){
return this._n
},
set n(newValue){
this._n = newValue
}
}
console.log(obj.n) //相当于调用 get 函数
obj.n = 10 //相当于调用 set 函数
console.log(obj.n)
console.log(obj._n)
使用 getter 和 setter 的时候不能在函数中返回或设置本身,这样很容易引起循环引用问题。设置了 getter 和 setter 函数的对象打印结果如下:
这样的打印结果和 Vue 处理后的打印结果是不是很相似呢,但是仅仅通过这些还是不能实现数据响应式,我们还得明白一件事,那就是数据代理。
三、数据代理
什么是数据代理?简单点说就是把对一个对象的操作(如读写操作)放在另一个对象上,我们只能操作另一个对象来间接改变本来的对象,而这另一个对象就是代理,类比我们租房的时候,本来是应该跟房东谈,但是房东全权交给房产中介代为管理,那么我们就只能去找房产中介了,看下列代码:
const myData = {
n: 1
};
function proxy(data) {
let obj = {};
Object.defineProperty(obj, "n", {
get() {
return data.n;
},
set(newValue) {
data.n = newValue;
}
});
return obj;
}
let obj = proxy(myData);
console.log(obj.n);
obj.n = 10;
console.log(myData);
该代码就实现了把对 myData 的读写放在了 obj 上,当我们访问 obj.n 的时候实际上返回的是 myData 的 n ,当我们设置 obj.n 的时候实际上设置的是 myData 的值,这样就实现了代理,但是你可能会问:如果仅仅是上述逻辑的话,我们也可以不通过代理,直接修改 myData 不就可以完全跳过代理吗?这样的确会完全跳过代理,所以我们需要进一步完善:
const myData = {
n: 1
};
function proxy(data) {
let _value=data.n;
let obj = {};
Object.defineProperty(data, "n", {
get() {
return _value;
},
set(newValue) {
_value = newValue;
}
});
Object.defineProperty(obj, "n", {
get() {
return data.n;
},
set(newValue) {
data.n = newValue;
}
});
let obj = proxy(myData);
return obj;
}
通过在代理函数中同时加上对原有对象的 getter 和 setter 函数,这样只要通过了代理,不论怎样修改对象的属性,我们都可以监测到,设想一下在上面的 set 函数内加上 render 函数呢,只要用户修改了这个值,就立刻调用 render 函数实现页面渲染,这样一来不就是 Vue 的数据响应式了嘛。
Vue 的数据响应式其实还更加复杂,本文只是简单深究了一下,若要深入研究,建议可以去看一下 Vue 的源码。
小生不才,希望各位多多指教!