# 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"!怎么了?

但是,如果删除代理,那么一切都会按预期进行。问题实际上出在代理中,在 (*)行。

  1. 当我们读取 admin.name,由于 admin 对象自身没有对应的的属性,搜索将转到其原型。
  2. 原型是 userProxy。
  3. 从代理读取 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");
最后更新: 11/26/2024, 1:31:53 PM