前言
记得最开始学习vue3的时候,讲到ref和reactive的区别,说是ref是用defineProperty实现的,reactive是用Proxy实现的。
最近刚好看到了一篇文章,里面的ref是通过reactive封装对象实现的。
当然这只是一个例子,但是不由产生了一个疑问,ref到底是怎么实现的呢?
RefImpl
带着上述疑问我去翻了vue3的源码
// packages\reactivity\src\ref.ts
// line:80
export function ref(value?: unknown) {
return createRef(value, false)
}
// line:97
function createRef(rawValue: unknown, shallow: boolean) {
// ...
return new RefImpl(rawValue, shallow)
}
// line:104
class RefImpl<T> {
private _value: T
// ...
constructor(value: T, public readonly __v_isShallow: boolean) {
// ...
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
//...
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
//...
}
}
这下就很清楚了,重点在于RefImpl,所以ref是通过es6类中的getter和setter实现的,并不是defineProperty。
在RefImpl中,通过将基本数据类型的值赋值给私有属性_value,然后使用get value()和set value(),对数据访问和变化进行监听。同时,在getter中使用trackRefValue收集依赖,在setter中使用triggerRefValue触发收集的响应函数,从而实现响应式。
区别
那么defineProperty和es6类中的getter和setter到底有什么区别呢?
区别一:enumerable
首先,两者创建的都是一个伪属性,或者说虚拟属性。也就是说,这个属性默认情况下无法被枚举。为什么是默认情况呢?因为defineProperty可以显示指定enumerable: true将属性变为可枚举。
参考以下代码:
let obj1 = {};
var name = "zhangsan";
Object.defineProperty(obj1, "name", {
get() {
console.log("get name");
return name;
},
set(val) {
console.log("set name");
name = val;
}
});
class Person {
_value = "";
constructor(name) {
this._value = name;
}
get name() {
console.log("get name");
return this._value;
}
set name(val) {
console.log("set name");
this._value = val;
}
}
let obj2 = new Person("zhangsan");
for (k in obj1) console.log(k); // 没有输出
for (k in obj2) console.log(k); // _value
为defineProperty增加enumerable: true:
Object.defineProperty(obj1, "name", {
get() {
console.log("get name");
return name;
},
set(val) {
console.log("set name");
name = val;
},
// enumerable: true, // 将name属性变为可枚举的,默认为false
});
for (k in obj1) console.log(k); // name
区别二:configurable
defineProperty创建的属性,默认无法被删除
delete obj1.name; // false
但是,当指定configurable: true时,可以通过delete删除属性。
将上述代码改为:
Object.defineProperty(obj1, "name", {
get() {
console.log("get name");
return name;
},
set(val) {
console.log("set name");
name = val;
},
configurable: true
});
delete obj1.name;
console.log(obj1.name); // undefined
可以看到name属性被删除了
而使用getter创建的属性,无法被删除
delete obj2.name; // true
// 虽然返回的是true,但是值仍然存在
console.log(obj2.name); // zhangsan
区别三:定义的时机
defineProperty可以在现有对象上定义,而setter和getter只能在类或者对象初始化的时候定义。
区别四:定义的位置
当定义在类上的时候,属性在实例的原型上,这也是无法通过delete删除属性的原因。
而defineProperty将属性定义在实例自身上。
console.log(Object.getOwnPropertyDescriptor(obj1, "name")); // {enumerable: false, configurable: true, get: ƒ, set: ƒ}
console.log(Object.getOwnPropertyDescriptor(obj2, "name")); // undefined
……欢迎补充