主要记录一下Object.defineProperty()和Proxy对象
1、Object.defineProperty()
语法:Object.defineProperty(obj, prop, descriptor)
;
obj: 要在其上定义属性的对象。 prop: 要定义或修改的属性的名称。 descriptor: 将被定义或修改的属性描述符。
返回被传递给函数的对象
1.1 属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。
MDN:
configurable:当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable:当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。 数据描述符同时具有以下可选键值:
writable:当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。
value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined。
get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
ser:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。
使用:
Object.defineProperty(obj, "key", {
enumerable: false,
configurable: false,
writable: false,
value: "static"
});
2、实现一个简易的vue数据劫持
使用Object.defineProperty的缺陷是性能,因为需要递归,而且无法监控数组长度的变化,而且对象如果后面有新增无法监控到,这也是vue有一个vm.$set
方法的原因。
function observer(obj) {
// 缺陷就是无法监控数组的变化
if (typeof obj !== "object" || obj == null) {
return;
}
for (let key in obj) {
// 因为defineProperty 需要一个公共的值去修改
defineReactive(obj, key, obj[key]);
}
}
let updateView = () => {
// 更新方法
console.log("更新");
};
function defineReactive(obj, key, value) {
// Object.defineProperty
observer(value); // 递归增加getter和setter
Object.defineProperty(obj, key, {
get() {
return value;
},
set(val) {
updateView();
value = val;
}
});
}
observer(obj);
obj.a.a = 100;
console.log(obj.a);
解释:
- 调用一个函数传入一个对象,如果不是对象的话就返回
- 如果是对象就去循环它,给它的每个值进行劫持。
- 每个值的值仍然可能是对象,所以递归一次,如果不是就默认返回了,如果是则进行这个流程。
- 给每个值数据劫持,如果是获取则返回,如果是更新则触发更新的方法。
3、proxy
ES6的新方法——代理: 好处是支持修改数组,可以自由决定是否拦截某个属性,性能也更高,缺点兼容性差.
语法:let p = new Proxy(target, handler);
target:用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。
返回一个proxy实例,可以再次操作。
let obj = {
a:{a:111}
}
let handler = { // 只能代理当前这个对象 1层, 如果多层只能递归。
get(target,key){
// 如果有多层,下一层的set方法会先来上一层的get里取值,所以我们在get里面加一层监控,如果来取值且为对象的话,就执行返回一个proxy,返回一个当前值的代理
if(typeof target[key] === 'object'){
return new Proxy(target[key],handler); // 如果是对象 就返回这个对象的代理
}
return Reflect.get(target,key);
},
set(target,key,value){
// target[key] = value;
if(key === 'length') return true;
return Reflect.set(target,key,value);
}
}
let proxy = new Proxy(obj, handler)
proxy.a.a = 222
console.log(obj.a.a);
3.1 Reflect 反射
proxy经常和Reflect组合使用。Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
常用方法:
- Reflect.get():获取对象身上某个属性的值,类似于 target[name]。
- Reflect.has():判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
- Reflect.set():将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
- Reflect.apply():对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。