Vue的一大特性就是响应式。这里的响应式指的是,当状态发生变化时,系统会自动更新关联状态。在Vue中的具体表现有:当数据发生改变时,触发视图重新渲染;computed属性在依赖值发生变化时,自动重新计算新值;提供watch监听器,可以监听到数据的变化。
这些都是怎么实现的呢?Vue2和Vue3中关于响应式实现的原理不太一样,Vue2使用ES5的defineProperty实现,而Vue3使用的是ES6的propxy.(PS:这也就是为什么Vue2不支持IE7/8,而Vue3不支持IE11.)
defineProperty实现
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法:
Object.defineProperty(obj, prop, descriptor)
- 第一个参数obj:要设置属性的对象;
- 第二个参数prop:要设置的属性名,这个属性可以是已存在也可以是不存在的;
- 第三个参数descriptor:该参数接收一个对象,用来对属性进行描述。如value(值),writable(是否可重写),enumerable(是否可枚举)等
举个?:
const student = {};
Object.defineProperty(student, 'age', {
value: 17,
writable: true
});
student.age = 18;
console.log(student.age);//打印出18
复制代码
这个例子里,定义了一个student对象。然后通过defineProperty给该对象定义了age属性,该属性值是可写的。所以,后面我们可以修改这个student对象age的值。
那之所以能够用来它实现响应式,是因为它的第三个参数,还提供了getter和setter方法。
get: 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。该函数的返回值会被用作属性的值。默认为 undefined。
set: 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。默认为 undefined。
举个?:
let student = {};
let age;
Object.defineProperty(student,'age',{
get:function() {
console.log('读取age');
return age;
},
set:function(val) {
console.log('设置age');
age = val;
}
});
复制代码
当对age进行设置和读值时:
也就是说,有了getter和setter,当某个属性被读取和设置时,我们可以进行拦截并做一些事情(比如重新渲染页面)。
如果我们想让对象的所有属性都具有响应式,就需要对全部属性进行遍历,实现getter和setter:
function convert (obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
Object.defineProperty(obj, key, {
get () {
console.log(`读取"${key}": ${internalValue}`)
return internalValue
},
set (newValue) {
console.log(`设置"${key}"为: ${newValue}`)
internalValue = newValue
}
})
})
}
复制代码
再进一步,如果对象的某个属性的值是一个数组或者对象,那么就还需要进行深度的遍历。
function convert(obj) {
Object.keys(obj).forEach((key) => {
let internalValue = obj[key]
//Object.defineProperty()...
if (typeof internalValue === "object") {
convert(internalValue);
}
});
}
复制代码
以上就是通过defineProperty实现响应式的主要原理。这种方法存在一个不足之处就是对于对象新增加的属性,仍然不具备响应式的特定。
Proxy实现
Vue3使用Proxy来实现响应式。先来看看MDN上的定义:
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
语法:
const p = new Proxy(target, handler)
1.第一个参数target:要包装的目标对象;
2.第二个参数handle:接收一个对象,内部定义了操作目标对象时的方法;
通过给对象设置代理,我们可以拦截对象属性的取值/赋值操作。
举个?:
const student = {
age: 23,
};
const handler = {
get(target, prop) {
console.log("读值:", prop);
return target[prop];
},
set(target, key, value) {
console.log("设置值", key, value);
target[key] = value;
return true;
},
};
const proxy = new Proxy(student, handler);
console.log(proxy.age);
复制代码
执行结果:
总结
通可见,虽然Vue3使用了ES6的新特性,但是基本思路还是跟Vue2一样的:通过拦截属性的取赋值进行数据的追踪与监听,从而实现数据变化触发页面的重新渲染。