# Reflect反射
反射(Reflect)向外界暴露了一些底层操作的默认行为,它是一个没有构造函数的内置对象,类似于 Math 对象,其所有方法都是 静态 的。
代理中的每个陷阱都会对应一个同名的反射方法(例如 Reflect.set()、Reflect.ownKeys()等),而每个反射方法又都会关联到对应代理所拦截的行为(例如 in 运算符、Object.defineProperty()等),这样就能保证某个操作的默认行为可随时被访问到。反射让对象的内置行为变得更加严谨、合理与便捷。
Reflect不是一个函数对象,因此它是不可构造的。Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上。
# Reflect.get(target, name[, receiver])
Reflect.get()方法与从 对象 (target[propertyKey]) 中读取属性类似
receiver: 如果target对象中指定了getter,receiver则为getter调用时的this值。
let exam = {
name: "Tom",
age: 24,
get info(){
return this.name + this.age;
}
}
Reflect.get(exam, 'name'); // "Tom"
// 当 target 对象中存在 name 属性的 getter 方法,
//getter 方法的 this 会绑定 receiver
let receiver = {
name: "Jerry",
age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20
// 当 name 为不存在于 target 对象的属性时,返回 undefined
Reflect.get(exam, 'birth'); // undefined
// 当 target 不是对象时,会报错
Reflect.get(1, 'name'); // TypeError
let user = {
name: "John",
};
user = new Proxy(user, {
get(target, prop, receiver) {
// console.log(target, prop, receiver)//{name: 'John'} 'name' Proxy {name: 'John'}
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver); // (1)
},
set(target, prop, val, receiver) {
console.log(`SET ${prop}=${val}`);
return Reflect.set(target, prop, val, receiver); // (2)
}
});
let name = user.name; // "GET name"
user.name = "Pete"; // "SET name=Pete"
console.log(user.name)//"GET name" Pete
# Reflect.set(target, name, value, receiver)
let exam = {
name: "Tom",
age: 24,
set info(value){
return this.age = value;
}
}
exam.age; // 24
Reflect.set(exam, 'age', 25); // true
exam.age; // 25
// value 为空时会将 age原有值改为undefined
Reflect.set(exam, 'age', ); // true
exam.age; // undefined
exam//{name: "Tom", age: undefined}
// 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定
// receiver , 所以修改的实际上是 receiver 的属性,
let receiver = {
age: 18
}
Reflect.set(exam, 'info', 1, receiver); // true
receiver.age; // 1
let receiver1 = {
name: 'oppps'
}
Reflect.set(exam, 'info', 1, receiver1);
receiver1.age; // 1
receiver1//{name: "oppps", age: 1}
# Reflect.has(obj, name)
是 name in obj 指令的 函数化 ,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。
let exam = {
name: "Tom",
age: 24
}
Reflect.has(exam, 'name'); // true
# Reflect.deleteProperty(obj, property)
是 delete obj[property] 的 函数化 ,用于删除 obj 对象的 property 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。
let exam = {
name: "Tom",
age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
exam // {age: 24}
// property 不存在时,也会返回 true
Reflect.deleteProperty(exam , 'name'); // true
# Reflect.construct(obj, args[,newobj])
等同于 new target(...args)。
function exam(name){
this.name = name;
}
Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
function exam(...name){
console.log(name)
}
Reflect.construct(exam, ['Tom5','Tony','sa']); // ["Tom5", "Tony", "sa"]
let target = function (webName, url) {
this.webName = webName;
this.url = url;
}
target.prototype.a = 1;
let newTarget = function () {
this.age = 4;
}
newTarget.prototype.b = 2;
let otarget = Reflect.construct(target, ["蚂蚁部落", "www.softwhy.com"], newTarget);
console.log(otarget.webName)
console.log(otarget.url)
console.log(otarget.age)
console.log(otarget.a)
console.log(otarget.b)
//蚂蚁部落
//www.softwhy.com
//undefined
//undefined
//2
实例otarget具有target构造函数中的属性,但是不具有target原型对象上的属性。实例otarget不具有newTarget构造函数中的属性,但是具有newTarget原型对象上的属性。
# Reflect.getPrototypeOf(obj)
用于读取 obj 的 _proto_属性。在 obj 不是对象时不会像 Object 一样把 obj 转为对象,而是会报错。
class Exam{}
let obj = new Exam()
Reflect.getPrototypeOf(obj) === Exam.prototype // true
let a='123'
console.log(typeof a)
console.log(a.substring(0,2))
console.log(a.__proto__=== String.prototype)//true
console.log(Reflect.getPrototypeOf(new String("12"))==String.prototype)//true
console.log(Reflect.getPrototypeOf("12")==String.prototype)//test.html:19 Uncaught TypeError
# Reflect.setPrototypeOf(obj, newProto)
用于设置目标对象的 prototype。
let obj={k:1}
var s={}
s.prototype={
a:function (){
console.log(1000)
}
}
Reflect.setPrototypeOf(obj, s.prototype)
obj.a()//1000
# Reflect.apply(func, thisArg, args)
等同于 Function.prototype.apply.call(func, thisArg, args) 。func 表示目标函数;thisArg 表示目标函数绑定的 this 对象;args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError 。
Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
Reflect.apply(Math.min, 'ss', [1, 3, 5, 3, 1]);//1
let antzone = {
url:"www.softwhy.com"
}
function func(webName) {
console.log(webName + "的url地址是:" + this.url);
}
Reflect.apply(func, antzone,["蚂蚁部落"])
//蚂蚁部落的url地址是:www.softwhy.com
# Reflect.defineProperty(target, propertyKey, attributes)
用于为目标对象定义属性。如果 target 不是对象,会抛出错误。
let myDate= {}
Reflect.defineProperty(myDate, 'now', {
value: () => Date.now()
}); // true
const student = {};
Reflect.defineProperty(student, "name", {value: "Mike"}); // true
student.name; // "Mike"
# Reflect.getOwnPropertyDescriptor(target, propertyKey)
用于得到 target 对象的 propertyKey 属性的描述对象。在 target 不是对象时,会抛出错误表示参数非法,不会将非对象转换为对象。
var exam = {}
Reflect.defineProperty(exam, 'name', {
value: true,
enumerable: false,
})
Reflect.getOwnPropertyDescriptor(exam, 'name')
// { configurable: false, enumerable: false, value: true, writable:
// false}
// propertyKey 属性在 target 对象中不存在时,返回 undefined
Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined
# Reflect.isExtensible(target)
用于判断 target 对象是否可扩展。返回值为 boolean 。如果 target 参数不是对象,会抛出错误。
let exam = {}
Reflect.isExtensible(exam) // true
# Reflect.preventExtensions(target)
用于让 target 对象变为不可扩展。如果 target 参数不是对象,会抛出错误。
let exam = {}
Reflect.preventExtensions(exam) // true
exam.a=1;
exam//{}
# Reflect.ownKeys(target)
用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。
var exam = {
name: 1,
[Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]
# Reflect注意
另一个对象 admin从 user 继承后,我们可以观察到错误的行为:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop]; // (*) target = user
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
// Expected: Admin
alert(admin.name); // 输出:Guest (?!?)
读取 admin.name 应该返回 "Admin",而不是 "Guest"!怎么了?
但是,如果删除代理,那么一切都会按预期进行。问题实际上出在代理中,在 (*)行。
- 当我们读取 admin.name,由于 admin 对象自身没有对应的的属性,搜索将转到其原型。
- 原型是 userProxy。
- 从代理读取 name 属性时,get 钩子会触发并从原始对象返回 target[prop] 属性,在 (*)行当调用 target[prop] 时,若 prop 是一个 getter,它将在 this=target 上下文中运行其代码。因此,结果是来自原始对象 target 的 this._name 即来自 user。
为了解决这种情况,我们需要 get 钩子的第三个参数 receiver。它保证传递正确的 this 给 getter。在我们的情况下是 admin。如何为 getter 传递上下文?对于常规函数,我们可以使用 call/apply,但这是一个 getter,它不是“被调用”的,只是被访问的。Reflect.get 可以做到的。如果使用它,一切都会正常运行。这是更正后的变体:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver); // (*)//如果删除了receiver,会得出guest这个结果
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
alert(admin.name); // Admin
现在 receiver,保留了对正确 this 的引用(即admin)的引用,该引用将在 (*) 行中使用Reflect.get传递给getter。我们可以将钩子重写得更短:
get(target, prop, receiver) {
return Reflect.get(...arguments);
}
Reflect 调用的命名方式与钩子完全相同,并且接受相同的参数。它们是通过这种方式专门设计的。因此, return Reflect... 会提供一个安全的提示程序来转发操作,并确保我们不会忘记与此相关的任何内容。
1)参数的检验更为严格,Object 的 getPrototypeOf()、isExtensible()等方法会将非对象的参数自动转换成相应的对象(例如,字符串转换成 String 对象,代码如下所示),而关联的反射方法却不会这么做,它会直接抛出类型错误。
Object.getPrototypeOf("strick") === String.prototype; //true
Reflect.getPrototypeOf("strick"); //类型错误
2)更合理的返回值,Object.setPrototypeOf()会返回它的第一个参数,而 Reflect 的同名方法会返回一个布尔值,后者能更直观地反馈设置是否成功,两个方法的对比如下所示。
var obj = {};
Object.setPrototypeOf(obj, String) === obj; //true
Reflect.setPrototypeOf(obj, String); //true
3)用方法替代运算符,反射能以调用方法的形式完成 new、in、delete 等运算符的功能,在下面的示例中,先使用运算符,再给出对应的反射方法。
function func() { }
new func();
Reflect.construct(func, []);
var people = {
name: "strick"
};
"name" in people;
Reflect.has(people, "name");
delete people["name"];
Reflect.deleteProperty(people, "name");