35.Proxy和Reflect详解
监听对对象的操作
- 有一个对象,我们希望监听这个对象中的属性被设置或获取的过程
- 下面实现的弊端:
- 如果我们想监听对对象更加丰富的操作,比如新增属性,删除属性,那么Object.defineProperty是无能为力的
使用Object.defineProperty()监听对对象进行操作
const obj = {
name: "coderwhy",
age: 18,
};
Object.keys(obj).forEach((key) => {
let value = obj[key];
Object.defineProperty(obj, key, {
set: function (newVal) {
value = newVal;
console.log(`监听到给${key}属性设置值`);
},
get: function () {
console.log(`监听到获取${key}属性的值`);
return value;
},
});
});
console.log(obj.name);
obj.age = 30;
// 监听到获取name属性的值
// coderwhy
// 监听到给age属性设置值
Proxy基本使用
在ES6中,新增了一个Proxy类,用于帮助我们创建一个代理:
- 如果我们希望监听对一个对象的相关操作,我们可以先创建一个这个对象的代理对象(Proxy对象)
- 之后对改对象的所有操作,都通过
代理对象
完成, - 代理对象可以监听到我们想要对原对象进行哪些操作
new Proxy(target, handler)
target为要监听的目标对象
Proxy对象有13种默认的捕获器方法,
如果需要监听对原对象的某种操作,然后做出相应处理,可以在handler对象中重写对应的捕获器方法
使用Proxy监听对对象进行操作
const obj = {
name: "coderwhy",
age: 18,
};
//创建一个obj的代理对象
const objProxy = new Proxy(obj, {
//重写proxy对象的get捕获器
get(target, key) {
console.log(`监听到获取obj对象的${key}属性的值`);
return target[key];
},
//重写proxy对象的set捕获器
set(target, key, newVal) {
console.log(`监听到obj对象的${key}属性的值被重新赋值了`);
target[key] = newVal;
},
});
//对obj对象要做的所有操作,都对其代理对象操作,
console.log(objProxy.name);
objProxy.age = 30;
// 监听到获取obj对象的name属性的值
// coderwhy
// 监听到obj对象的age属性的值被重新赋值了
Proxy的13中捕获器用法
const obj = {
name: "coderwhy",
age: 18,
};
//创建一个obj的代理对象
const objProxy = new Proxy(obj, {
//重写proxy对象的get捕获器:监听获取目标对象的属性值
get(target, key, receiver) {
//get捕获器有三个参数:
//target为目标对象,key为当前操作的属性名,receiver为目标对象的代理对象即objProxy
console.log(`监听到获取obj对象的${key}属性的值`);
return target[key];
},
//重写proxy对象的set捕获器:监听设置目标对象的属性值
set(target, key, newVal, receiver) {
//set捕获器有四个参数:
//target为目标对象,key为当前操作的属性名,newVal为给此属性设置的新值,receiver为目标对象的代理对象即objProxy
console.log(`监听到obj对象的${key}属性的值被重新赋值了`);
target[key] = newVal;
},
//监听对对象进行的in操作
has: function (target, key) {
console.log(`监听到对obj对象的${key}属性的in操作`);
return key in target;
},
//监听对对象的delete操作的捕获器
deleteProperty: function (target, key) {
console.log(`监听到对obj对象的${key}属性的delete操作`);
delete target[key];
},
});
//对obj对象要做的所有操作,都对其代理对象操作,
//获取对象的属性值
console.log(objProxy.name);
//设置对象的属性值
objProxy.age = 30;
// in操作
console.log("name" in objProxy);
//delete操作
delete objProxy.age;
console.log(obj);
proxy对函数对象的监听
function foo(name, age) {
this.name = name;
this.age = age;
}
//创建foo的代理对象
const fooProxy = new Proxy(foo, {
//监听对函数对象的apply调用
apply(target, thisArg, argArray) {
//target为目标对象,thisArg为给目标函数绑定的this,argArray为传给目标函数的参数数组
console.log("对foo进行了apply调用");
return target.apply(thisArg, argArray);
},
//监听对函数对象的new调用
construct(target, argArray) {
//target为目标对象,argArray为传给目标函数的参数数组
console.log("对foo进行了new调用");
return new target(...argArray);
},
});
fooProxy.apply({}, ["why", "18"]);
new fooProxy("lily", 30);
另外七种捕获器用法:
下面为知识点扩展:
-
Object.getPrototypeOf(target)
- target 目标对象
- 获取目标对象的proto原型对象
-
Object.setPrototypeOf(target, prototype)
- target 目标对象
- 设置目标对象的proto原型对象为prototype
-
Object.getOwnPropertyDescriptor(target, prop)
- 返回指定对象上一个自有属性对应的属性描述符。
- target 目标对象
- prop 目标属性
-
Reflect.ownKeys(target) 方法
- 返回一个由目标对象自身的属性键组成的数组。
- 它的返回值等同Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
-
Object.isExtensible(target) 方法
- 判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
-
Object.preventExtensions(target)方法
- 让一个对象变的不可扩展,也就是永远不能再添加新的属性。
const obj = { name: "why", age: 18 };
const objProxy = new Proxy(obj, {
//监听对目标对象进行的Object.getPrototypeOf操作
getPrototypeOf(target) {
console.log("监听到了对obj进行的Object.getPrototypeOf操作");
return Object.getPrototypeOf(target);
},
setPrototypeOf(target, prototype) {
// target为目标对象 prototype为要被设置为目标对象原型对象的对象
console.log("监听到了对obj进行的Object.setPrototypeOf操作");
return Object.setPrototypeOf(target, prototype);
},
getOwnPropertyDescriptor(target, prop) {
console.log("监听到了对obj进行的Object.getOwnPropertyDescriptor操作");
return Object.getOwnPropertyDescriptor(target, prop);
},
defineProperty(target, property, descriptor) {
console.log("监听到了对obj进行的Object.defineProperty操作");
return Object.defineProperty(target, property, descriptor);
},
//监听对目标对象进行的Object.getOwnPropertySymbols或Object.getOwnPropertyNames或Reflect.ownKeys操作
ownKeys(target) {
console.log(
"监听到了对obj进行的Object.getOwnPropertyNames/Object.getOwnPropertySymbols操作"
);
return Object.getOwnPropertyNames(target);
},
isExtensible(target) {
console.log("监听到了对obj进行的Object.isExtensible操作");
return Object.isExtensible(target);
},
preventExtensions(target) {
console.log("监听到了对obj进行的Object.preventExtensions操作");
return Object.preventExtensions(target);
},
});
const objPrototype = Object.getPrototypeOf(objProxy);
console.log(objPrototype); // [Object: null prototype] {}
Object.setPrototypeOf(objProxy, { title: "讲师" });
Object.getOwnPropertyDescriptor(objProxy, "name");
Object.defineProperty(objProxy, "height", {
value: "1.88",
writable: true,
enumerable: true,
configurable: true,
});
Object.getOwnPropertyNames(objProxy);
Object.getOwnPropertySymbols(objProxy);
console.log(Reflect.ownKeys(objProxy));
console.log(Object.isExtensible(objProxy));
Object.preventExtensions(objProxy);
console.log(Object.isExtensible(objProxy));
知识拓展:
例:
const s = Symbol();
const obj = {
name: "why",
age: 30,
[s]: "我是灵魂画手",
};
console.log(Reflect.ownKeys(obj)); //[ 'name', 'age', Symbol() ]
console.log(Object.getOwnPropertyNames(obj)); //[ 'name', 'age' ]
console.log(Object.getOwnPropertySymbols(obj)); //[ Symbol() ]
console.log(
Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
); //[ 'name', 'age', Symbol() ]
Reflect
Reflect也是ES6新增的一个API,它是一个对象
,字面的意思是反射。
那么这个Reflect有什么用呢?
- 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
- 比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();
- 比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty() ;
如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
- 这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
- 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
- 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;
- 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;
那么Object和Reflect对象之间的API关系,可以参考MDN文档:
https://developer.mozilla.org/zh�CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods
下表详细介绍了Object 和 Reflect API上可用方法之间的差异。请注意,如果API中不存在某种方法,则将其标记为N/A。
image.png
image.png
let s = Symbol();
const obj = {
_name: "why",
get name() {
return this._name;
},
set name(newVal) {
this._name = newVal;
},
s: "不可描述的秘密",
};
//为对象obj添加一个不可枚举属性work
Object.defineProperty(obj, "work", {
value: "teacher",
});
// console.log(Object.getOwnPropertyDescriptors(obj))
console.log(Object.keys(obj)); // [ '_name', 'name', 's' ]
//Object.keys(target)返回目标对象所有可枚举属性名组成的数组
console.log(Reflect.ownKeys(obj)); //[ '_name', 'name', 's', 'work' ]
//Reflect.ownKeys(target)返回目标对象素有的属性名组成的数组
Refect和Proxy结合使用
const obj = {
name: "why",
age: 18,
};
const objProxy = new Proxy(obj, {
get: function (target, key) {
console.log(`监听到获取对象的${key}的属性值`)
return Reflect.get(target, key);
},
set: function (target, key, newVal) {
//Reflect.set的执行返回一个布尔值,如果设置值成功,为true,如果失败为false
const result = Reflect.set(target, key, newVal);
if (result) {
console.log("设置值成功");
} else {
console.log("设置值失败");
}
},
});
objProxy.name = 'lily'
console.log(objProxy.name)
Reflect的receiver参数
const obj = {
_name: "why",
get name() {
return this._name;
},
set name(newVal) {
this._name = newVal;
},
};
const objProxy = new Proxy(obj, {
get(target, key) {
console.log(`监听到了获取对象的${key}的值的操作`)
return Reflect.get(target, key)
}
})
console.log(objProxy.name)
image.png
分析:
- objProxy.name 的执行会触发Proxy对象的get捕获器,执行Relect.get(target,key)
- Relect.get(target,key)的执行 会执行对象的name属性的存取属性描述符的get方法,从而执行this._name,
- this._name中的this为obj,则是通过obj._name获取_name属性的值,
- 而没有通过代理对象获取_name属性的值==》objProxy._name,所以不会再触发Proxy的get捕获器
- 而实际上,我们希望捕获每一次获取对象属性的值的操作,
- 那么需要把对象的name属性的属性描述符的get方法中的this改为Proxy对象objProxy
Reflect.get(target, key, receiver)方法接受三个参数,
- target: 操作的目标对象
- key: 操作的属性
- receiver: 如果target对象中指定了getter,receiver则为getter调用时的this值。
所以可以 通过给Reflect.get传递receiver来改变目标对象中getter调用时的this指向
而Proxy对象的get捕获器接收的第三个参数receiver为该Proxy对象
const obj = {
_name: "why",
get name() {
return this._name;
},
set name(newVal) {
this._name = newVal;
},
};
const objProxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(`监听到了获取对象的${key}的值的操作`)
return Reflect.get(target, key, receiver)
}
})
console.log(objProxy.name)
image.png
Reflect.construct(target, argArray[, newTarget])
- Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(...argArray).
- target
被运行的目标构造函数
- argArray
类数组,目标构造函数调用时的参数。 - newTarget 可选
作为新创建对象的原型对象的constructor属性, 参考 new.target 操作符,默认值为target - 返回值
以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。
function Student(name, age) {
this.name = name;
this.age = age;
}
function Teacher() {}
const stu = new Student("lily", 18);
console.log(stu); //Student { name: 'lily', age: 18 } 可以看到创建了一个Student类的对象
console.log(stu.__proto__ === Student.prototype); //true
//现在有一个需求,执行Student方法,但是创建出来的是Teacher类的对象
var stu1 = Object.create(Teacher.prototype); //创建一个空对象,其__proto__指向Teacher.prototype
Student.apply(stu1, ["lily", 18]);
console.log(stu1); // Teacher { name: 'lily', age: 18 }
console.log(stu1.__proto__ === Teacher.prototype); //true
//下面的方式与上面的方式等效
const stu2 = Reflect.construct(Student, ["lily", 18], Teacher);
console.log(stu2); // Teacher { name: 'lily', age: 18 }
console.log(stu2.__proto__ === Teacher.prototype); //true
非常感谢王红元老师的深入JavaScript高级语法让我学习到很多 JavaScript
的知识