在了解 Reflect
和 Proxy
之前,需要知道 JavaScript 规范以及引擎带有一系列的内部方法,例如 [[Get]]
、[[Set]]
、[[Prototype]]
等。这类用双 []
包裹的属性被称为"内部槽和内部方法"(Internal slots and methods)。它们不是对象的属性,不能作为 JavaScript 代码的属性直接访问。
什么是内部槽和内部方法?
内部槽 是 JavaScript 对象的数据成员或规范类型,用于存储对象的状态。例如,每个 JavaScript 对象都有一个 [[Prototype]]
内部槽,用于指向对象的原型。
内部方法 是 JavaScript 对象的成员函数。例如,每个 JavaScript 对象都有一个 [[GetOwnProperty]]
内部方法,由引擎在需要获取对象自身属性时调用。
这些内部槽和内部方法是 JavaScript 引擎内部实现的一部分,并不直接暴露给开发者。相反,它们通过规范来定义其行为和要求,从而保证不同引擎实现的一致性。
为什么内部槽和内部方法很重要?
内部槽和内部方法有助于确保 JavaScript 对象的一致行为。在不同的 JavaScript 引擎(如 V8、SpiderMonkey 等)中,尽管每个引擎可能有不同的实现方式,它们都必须按照同一套规范来实现这些内部槽和内部方法。
这使得 JavaScript 程序能够在不同的环境中表现出一致的行为,从而提高了代码的跨平台兼容性和可移植性。
常见的内部方法
内部方法 | 签名 | 描述 |
---|---|---|
[[GetPrototypeOf]] | () → Object / Null | 获取对象的原型。返回提供继承属性的对象,若无,则返回null 。对应方法:Object.getPrototypeOf() |
[[SetPrototypeOf]] | (Object / Null) → Boolean | 设置对象的原型。将对象与另一个提供继承属性的对象关联。成功返回true ,失败返回false 。对应方法:Object.setPrototypeOf() |
[[IsExtensible]] | () → Boolean | 判断对象是否可扩展。对应方法:Object.isExtensible() |
[[PreventExtensions]] | () → Boolean | 防止对象扩展。控制新属性是否能被加入对象内。成功返回true ,失败返回false 。对应方法:Object.preventExtensions() |
[[GetOwnProperty]] | (propertyKey) → Undefined / Property Descriptor | 获取对象自身属性的属性描述符。若无对应属性,返回undefined 。对应方法:Object.getOwnPropertyDescriptor() 和 Object.getOwnPropertyDescriptors() |
[[DefineOwnProperty]] | (propertyKey, PropertyDescriptor) → Boolean | 定义或修改对象自己的属性。成功返回true ,失败返回false 。对应方法:Object.defineProperty() 和 Object.defineProperties() |
[[HasProperty]] | (propertyKey) → Boolean | 判断对象是否拥有自身或继承的某个属性。对应方法:Object.prototype.hasOwnProperty() |
[[Get]] | (propertyKey, Receiver) → any | 获取对象中指定属性的值。如果需要执行ECMAScript代码来获得属性值,Receiver 被当作 this 使用。 |
[[Set]] | (propertyKey, value, Receiver) → Boolean | 设置对象中指定属性的值。Receiver 被当作 this 使用。成功返回true ,失败返回false 。 |
[[Delete]] | (propertyKey) → Boolean | 删除对象中指定属性。成功返回true ,失败返回false 。类似于 JavaScript 中的 delete 关键字。 |
[[OwnPropertyKeys]] | () → List of propertyKey | 返回包含对象自身所有属性键的列表。对应方法:Object.getOwnPropertyNames() 和 Object.getOwnPropertySymbols() |
函数对象支持的其他基本内部方法
内部方法 | 签名 | 描述 |
---|---|---|
[[Call]] | (any, a List of any) → any | 执行与对象关联的代码。通过函数调用表达式调用。第一个参数表示 this ,第二个参数是传入的参数列表。实现此内部方法的对象是可调用的。 |
[[Construct]] | (a List of any, Object) → Object | 创建一个对象。通过 new 或 super 操作调用。第一个参数是包含运算符参数的列表,第二个参数是初始应用对象。实现该内部方法的对象称为 constructors 。非构造函数对象没有 [[Construct]] 内部方法。 |
2. Proxy 和 Reflect 的作用
JavaScript 提供了 Proxy
和 Reflect
对象来拦截和操作这些内部方法的行为。
description: Reflect反射
因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了 Object
上面,Object
作为一个构造函数,这些操作实际上放到它身上并不合适,还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的
在ES6中新增了 Reflect
,让我们这些操作都集中到了Reflect对象上,它提供了一些反射方法,这些方法与那些在 Object
和 Function
原型上的方法具有相同的名称和功能。Reflect 的引入主要是为了使操作对象的行为变得更规范和一致,并且提供一个与 Proxy
对象互补的 API。
Object.getOwnPropertyNames(Reflect)
获取到'Reflect' 上的属性,因为这些属性都是不可枚举的因此没有使用'Object.keys' 来获取,一共获取了13个静态方法
['defineProperty', 'deleteProperty', 'apply', 'construct', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', 'has', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf']
Reflect
13个静态方法大致可以分为三类:对象操作、函数调用和原型操作。
对象操作方法
- Reflect.get(target, propertyKey[, receiver]) :获取对象的属性值,相当于
target[propertyKey]
。 - Reflect.set(target, propertyKey, value[, receiver]) :设置对象的属性值,相当于
target[propertyKey] = value
。 - Reflect.deleteProperty(target, propertyKey) :删除对象的属性值,相当于
delete target[propertyKey]
。 - Reflect.has(target, propertyKey) :检查对象是否有某个属性,相当于
propertyKey in target
。 - Reflect.defineProperty(target, propertyKey, descriptor) :定义对象的属性,相当于
Object.defineProperty(target, propertyKey, descriptor)
。 - Reflect.getOwnPropertyDescriptor(target, propertyKey) :获取对象自有属性的描述符,相当于
Object.getOwnPropertyDescriptor(target, propertyKey)
。 - Reflect.ownKeys(target) :返回对象的所有自有属性的键,相当于
Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。
函数调用方法
- Reflect.apply(target, thisArgument, argumentsList) :调用一个函数,相当于
Function.prototype.apply.call(target, thisArgument, argumentsList)
。 - Reflect.construct(target, argumentsList[, newTarget]) :构造一个实例,相当于
new target(...argumentsList)
。
原型操作方法
- Reflect.getPrototypeOf(target) :获取对象的原型,相当于
Object.getPrototypeOf(target)
。 - Reflect.setPrototypeOf(target, prototype) :设置对象的原型,相当于
Object.setPrototypeOf(target, prototype)
。 - Reflect.isExtensible(target) :检查对象是否是可扩展的,相当于
Object.isExtensible(target)
。 - Reflect.preventExtensions(target) :让一个对象变得不可扩展,相当于
Object.preventExtensions(target)
。
使用案例
Reflect.get(target, name, receiver)
好的,我来优化这段文字,使其更容易理解。
-
Reflect.get(target, propertyKey, [receiver])
方法的作用类似于从对象中读取属性(如target[propertyKey]
),但它是通过一个函数来实现的。如果propertyKey
对应的属性是一个 getter 函数,receiver
参数将被作为this
使用。如果没有提供receiver
参数,那么this
将指向target
。需要注意的是,只有在属性是 getter 函数时 ,receiver
才能改变this
的指向。 -
调用
Reflect.get()
类似于使用target[propertyKey]
表达式,因为它也会查找目标对象原型链上的属性值。如果目标对象上不存在该属性,则返回undefined
。
js
const a = {
name: "w",
age: "18",
get info() {
return this.name + this.age;
},
};
console.log(Reflect.get(a, "name")); // w
console.log(Reflect.get(a, "info")); // w18
// 别的对象借用获取属性
console.log(Reflect.get(a, "info", { name: "z", age: "20" })); // z20
Reflect.set(target, propertyKey, value[, receiver])
等同于 target[propertyKey] = value
js
// 设置对象的属性值
Reflect.set(obj, 'b', 2);
console.log(obj.b); // 2
Reflect.deleteProperty(target, propertyKey)
对应的内部插槽 [[Delete]]
方法等同于delete obj[name]
,用于删除对象的属性,方法的第一个参数不是对象,会报错 Reflect.deleteProperty(target, propertyKey)
js
var obj = { x: 1, y: 2 };
Reflect.deleteProperty(obj, "x"); // true
obj; // { y: 2 }
var arr = [1, 2, 3, 4, 5];
Reflect.deleteProperty(arr, "3"); // true
arr; // [1, 2, 3, , 5]
// 如果属性不存在,返回 true
Reflect.deleteProperty({}, "foo"); // true
// 如果属性不可配置,返回 false
Reflect.deleteProperty(Object.freeze({foo: 1}), "foo"); // false
Reflect.has
Reflect.has
方法检查target对象或其原型上的propertyKey属性是否存在。这与in操作符完全相同。如果找到属性,则返回true,否则返回false,对应内部插槽 [[HasProperty]]
js
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
Reflect.ownKeys(target)
Reflect.ownKeys
使用的内部插槽 [[OwnPropertyKeys]]
,方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。结合了 Object.getOwnPropertyNames
和 Object.getOwnPropertySymbols
的结果,返回对象自身的所有属性键,包括字符串键和符号键。即自身所有属性(包含可枚举不可枚举和 symbol 属性)
Object.keys(obj)
:返回一个数组,包含对象自身的所有可枚举属性键,但不包括符号键。Object.getOwnPropertyNames(obj)
:返回一个数组,包含对象自身的所有字符串属性键,但不包括符号键。Object.getOwnPropertySymbols(obj)
:返回一个数组,包含对象自身的所有符号属性键。
js
const obj = {
property1: 42,
[Symbol("property2")]: "symbol value",
say() {},
};
// 增加不可枚举
Object.defineProperty(obj, "hidden", {
value: "not enumerable",
enumerable: false,
});
// 获取目标对象的所有属性键(包括符号键)
const keys = Reflect.ownKeys(obj);
console.log(keys); // [ 'property1', 'say', 'hidden', Symbol(property2) ]
// 只能可枚举的
console.log(Object.keys(obj)); // [ 'property1', 'say' ]
// 不能获取symbol的
console.log(Object.getOwnPropertyNames(obj)); // [ 'property1', 'say', 'hidden' ]
// 只能获取symbol的
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(property2) ]
- 注意包含对象自身的所有符号属性键 用继承来说明
js
class Base {
constructor() {
this.baseProperty = "base";
}
baseMethod() {}
}
class Derived extends Base {
constructor() {
super();
this.derivedProperty = "derived";
}
[Symbol("property2")] = "symbol value";
derivedMethod() {}
}
const derivedObj = new Derived();
const keys = Reflect.ownKeys(derivedObj);
// 制造 可枚举的比较复杂 这里数据没有用可枚举
console.log(keys); // [ 'baseProperty', 'derivedProperty', Symbol(property2) ]
console.log(Object.keys(derivedObj)); // [ 'baseProperty', 'derivedProperty' ] // 只能可枚举的属性
console.log(Object.getOwnPropertyNames(derivedObj)); // [ 'baseProperty', 'derivedProperty' ] // 不能获取symbol属性
console.log(Object.getOwnPropertySymbols(derivedObj)); // [ Symbol(property2) ]
如上图如果你想获取继承的属性 console.log(Reflect.ownKeys(Reflect.getPrototypeOf(derivedObj)));
不停从原型链获取即可
Reflect.defineProperty(target, propertyKey, attributes)
Reflect.defineProperty(target, propertyKey, attributes)
使用的内部插槽[[DefineOwnProperty]]
Object.defineProperty
和 Reflect.defineProperty
两者在功能上非常相似,但它们在返回值和异常处理上有所不同。我们来详细对比一下:
-
Object.defineProperty
如果操作成功,返回目标对象obj
。如果操作失败,抛出一个TypeError
异常。 -
Reflect.defineProperty
如果操作成功,返回true
。 如果操作失败,返回false
。
javascript
// 使用 Object.defineProperty
try {
Object.defineProperty({}, 'key', { value: 42, writable: false });
console.log('属性定义成功');
} catch (e) {
console.error('Object.defineProperty 失败:', e);
}
// 使用 Reflect.defineProperty
const success = Reflect.defineProperty({}, 'key', { value: 42, writable: false });
if (success) {
console.log('属性定义成功');
} else {
console.log('Reflect.defineProperty 失败');
}
Reflect.getOwnPropertyDescriptor(target, propertyKey)
返回目标对象的非继承属性的属性描述符,即非 [[Prototype]](__proto__)
上的属性描述符。和 Object.getOwnPropertyDescriptor
有略微的不同
-
Object.getOwnPropertyDescriptor(target, propertyKey)
对非对象的target
参数会被强制转换为对象。 -
Reflect.getOwnPropertyDescriptor
target
必须是一个对象。如果传入非对象类型,会抛出异常(TypeError
)。javascriptconst obj = { key: 42 }; const descriptor = Reflect.getOwnPropertyDescriptor(obj, 'key'); console.log(descriptor); // 输出: { value: 42, writable: true, enumerable: true, configurable: true } // 非对象的情况会抛出 TypeError try { Reflect.getOwnPropertyDescriptor(42, 'key'); } catch (e) { console.error(e); // TypeError: Reflect.getOwnPropertyDescriptor called on non-object }
javascriptconst obj = { key: 42 }; const descriptor = Object.getOwnPropertyDescriptor(obj, 'key'); console.log(descriptor); // 输出: { value: 42, writable: true, enumerable: true, configurable: true } // 基本类型会被强制转换为对象 const strDescriptor = Object.getOwnPropertyDescriptor('foo', 'length'); console.log(strDescriptor); // 输出: { value: 3, writable: false, enumerable: false, configurable: false }
js
Reflect.getOwnPropertyDescriptor({x: "hello"}, "x");
// {value: "hello", writable: true, enumerable: true, configurable: true}
Reflect.getOwnPropertyDescriptor({x: "hello"}, "y");
// undefined
Reflect.getOwnPropertyDescriptor([], "length");
// {value: 0, writable: true, enumerable: false, configurable: false}
// ------------------------------ 继承属性 ---------------------------------------
const sy = Symbol('a')
class A {
name = 123;
[sy] = 145
getName() {}
}
class B extends A {
getAge() {}
}
const b = new B()
console.log(Reflect.getOwnPropertyDescriptor(b, 'getName'))
console.log(Reflect.getOwnPropertyDescriptor(b, 'getAge'))
console.log(Reflect.getOwnPropertyDescriptor(b, 'name'))
// // 打印结果:
// undefined
// undefined
// { value: 123, writable: true, enumerable: true, configurable: true }
Reflect.getPrototypeOf(target)
1.静态方法 Reflect.getPrototypeOf(target)
与 Object.getPrototypeOf()
方法几乎是一样的。都是返回指定对象的原型(即内部的 [[Prototype]]
属性的值)。如果目标没有原型,则返回null。 Reflect
要求target 必须是对象,Object 则不用 会自动进行拆箱转换
js
Reflect.getPrototypeOf( null) // TypeError: Reflect.getPrototypeOf called on non-object
Reflect.getPrototypeOf( 'hello') // TypeError: Reflect.getPrototypeOf called on non-object
Reflect.getPrototypeOf( {}) // Object {constructor: Object(), __defineGetter__: ƒ, ...}
Reflect.setPrototypeOf(target, prototype)
Reflect.setPrototypeOf(target, prototype)
,返回一个 Boolean 值表明是否原型已经成功设置,内部的 [[Prototype]]
属性值对应 Object.setPrototypeOf()
js
const object1 = {};
console.log(Reflect.setPrototypeOf(object1, Object.prototype));
// expected output: true
console.log(Reflect.setPrototypeOf(object1, null));
// expected output: true
const object2 = {};
console.log(Reflect.setPrototypeOf(Object.freeze(object2), null));
// expected output: false
Reflect.preventExtensions(target)
在实际开发中,Reflect.preventExtensions
可以用来保护对象,使其不能再添加新的属性。虽然现有属性仍然可以修改或删除,但它提供了一种基本的对象保护机制,帮助你更好地控制对象的结构和类型。
Reflect.preventExtensions
返回操作成功的布尔值。Object.preventExtensions
返回修改后的对象。- 如果参数不是对象,
Reflect.preventExtensions
会抛出异常,而Object.preventExtensions
会进行类型转换。
js
let obj = {name: "Alice"};
// 默认对象是可扩展的
console.log(Reflect.isExtensible(obj)); // 输出: true
// 使用 Reflect.preventExtensions 使对象不可扩展
Reflect.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); // 输出: false
// 尝试添加新属性会失败
obj.age = 30;
console.log(obj.age); // 输出: undefined (属性添加失败)
// 现有属性仍然可以修改
obj.name = "Bob";
console.log(obj.name); // 输出: "Bob"
// 现有属性可以删除
delete obj.name;
console.log(obj.name); // 输出: undefined
Reflect.isExtensible(target)
Reflect.isExtensible(target)
target 必须是一个对象如果是非对象会异常报错,对应的内部插槽 [[IsExtensible]]
,检查对象是否可以进行扩展,返回一个 Boolean 值表明目标对象是否可扩展对应的方法,Object.isExtensible()
方法, 不同点是它对非对象的 target 参数将被强制转换为对象。
对象的"可扩展性"指的是是否可以向对象添加新的属性。默认情况下,所有对象都是可扩展的。但是,使用某些方法(如 Object.preventExtensions
、Object.seal
或 Object.freeze
)可以防止对象的扩展。
Object.preventExtensions(obj)
: 使对象不可扩展。Object.seal(obj)
: 使对象不可扩展且所有现有属性不可配置。Object.freeze(obj)
: 使对象不可扩展、所有现有属性不可配置且不可写。
js
let obj = {};
// 默认对象是可扩展的
console.log(Reflect.isExtensible(obj)); // 输出: true
// 停止对象的扩展
Object.preventExtensions(obj);
console.log(Reflect.isExtensible(obj)); // 输出: false
// 使用 Object.freeze 冻结对象也会使其不可扩展
let frozenObj = Object.freeze({});
console.log(Reflect.isExtensible(frozenObj)); // 输出: false
// Object.seal 同样会使对象不可扩展
let sealedObj = Object.seal({});
console.log(Reflect.isExtensible(sealedObj)); // 输出: false
Reflect.apply(target, thisArgument, argumentsList)
1. Reflect.apply
的作用: Reflect.apply
对应 JavaScript 内部的 [[call]]
插槽,用于调用一个函数。其作用等同于 ES5 时期的 Function.prototype.apply.call
,特别是在自定义函数的 apply
方法被重写但仍希望调用其原生 apply
方法时,Reflect.apply
更为简洁和直观。
2. Function.prototype.apply.call
的解析:
apply
方法 :接受两个参数,第一个是thisArg
(函数运行时的this
值),第二个是传给函数参数的数组
。call
方法 :接受多个参数,第一个参数是thisArg
,之后是函数的参数分别列出。
例如:
javascript
Function.prototype.apply.call(someFunc, thisArg, [argsArray]);
等同于:
javascript
someFunc.apply(thisArg, argsArray);
3. Reflect.apply
的简化: 使用 Reflect.apply(target, thisArg, args)
:
target
:目标函数thisArg
:函数调用时的this
值args
:调用时的参数数组
4. 参数处理差异:
- 在
Function.prototype.apply
中,第二个参数可以为null
或undefined
,函数仍会正常执行。 - 在
Reflect.apply
中,如果参数数组为null
或undefined
会抛出TypeError
异常。
示例:
javascript
getName.apply(null, null); // 正常执行
Reflect.apply(getName, null, null); // TypeError 异常
Reflect.apply(getName, null, ""); // TypeError 异常
Reflect.apply(getName, null); // TypeError 异常
// 如果不传参数,应该这样写
Reflect.apply(getName, null, {}); // 或
Reflect.apply(getName, null, []); // 正常执行
const obj = { f: 'w' }
var f = 'windows'
function getName(age) {
console.log(age, this.f)
}
getName(12)
getName.apply(obj, [12])
Reflect.apply(getName, null, [12])
Reflect.apply(getName, obj, [12])
Function.prototype.apply.call(getName, obj, [12])
// 打印结果
12 'windows'
12 'w'
12 'windows'
12 'w'
12 'w'
Reflect.construct(target, argumentsList, [, newTarget])
Reflect.construct(target, argumentsList[, newTarget])
用于创建一个新的实例对象,并且该对象的原型是由 newTarget
确定的。
-
代表目标构造函数的第一个参数。
-
第二个参数,代表目标构造函数的参数,以类数组格式传递。
-
可选参数,指定新创建对象的原型对象的
constructor
属性。如果不提供,默认值为target
。
Reflect.construct
映射到 JavaScript 内部的 [[Constructor]]
插槽,等同于以下代码:
javascript
var obj = new Foo(...args);
和 Reflect.apply
一样,如果不需要对目标函数传参,那么需要传一个空数组,否则会报错。
javascript
Reflect.construct(target, null); // TypeError 异常
Reflect.construct(target); // TypeError 异常
Reflect.construct(target, []); // 不传参的情况
使用第三个参数 newTarget
可以改变新创建对象的 [[Prototype]]
指向,从而改变 __proto__
。这意味着新创建的对象构造函数可以指向 newTarget
,如下所示:
javascript
class OneClass {
constructor() {
this.name = 'OneClass';
}
}
class OtherClass {
constructor() {
this.name = 'OtherClass';
}
}
// 生成一个对象,其 `[[Prototype]]` 指向 `OtherClass.prototype`
let obj = Reflect.construct(OneClass, [], OtherClass);
console.log(obj instanceof OtherClass); // true
console.log(obj instanceof OneClass); // false
console.log(obj.name); // "OneClass"
这等同于如下代码:
javascript
// 创建一个以 `OtherClass.prototype` 为原型的对象
var obj2 = Object.create(OtherClass.prototype);
// 调用 `OneClass`,将 `this` 指向新对象
OneClass.apply(obj2, []);
注意:上述代码实际上调用的是 OneClass
的内部 [[Call]]
插槽,而不是 [[Constructor]]
,this
绑定到了新创建的对象上,因此 new.target
在这种情况下为 undefined
,这和使用 Reflect.construct
是不同的。
js
class A {
name = 'a'
age = 'b'
getAge() {}
}
class B {
zz = 12
getName() {}
}
const b = Reflect.construct(A, {}, B)
console.log(b instanceof A) // false
console.log(b instanceof B) // true