元编程 (Meta Programming) 是指编写可以操作或改变其他程序的程序。元编程可以改变 JavaScript 的一些基本操作的行为
主要与这三个对象有关:
Symbol
:通过内置 Symbol 值复写 JS 语言中的基本操作Proxy
:通过钩子函数拦截、改变 JS 语言的基本操作Reflect
:获取 JS 语言内部的基本操作
什么是基本操作
基本操作包括:属性访问和赋值、属性删除、属性枚举、函数调用、对象构造等
属性访问 (get) 和赋值 (set)
使用 .
或 []
访问对象的属性,使用 =
给属性赋值
let obj = { a: 1 }
console.log(obj.a) // 属性访问
obj.b = 2 // 属性赋值
console.log(obj['b']) // 属性访问
属性删除
let obj = { a: 1 }
delete obj.a
属性枚举
const obj = {
name: 'test',
age: 18,
}
for (k in obj) {
console.log(obj[k])
}
函数调用
function add(a, b) {
return a + b
}
const result = add(1, 2)
对象构造
function Person(name, age) {
this.name = name
this.age = age
}
const alice = new Person('Alice', 25)
Symbol
通过内置 Symbol 值复写 JS 语言中的内部实现
Reflect
Reflect 是一个内置的全局对象,对一些函数式的操作提供了面向对象的 API。 Reflect 不是一个函数对象,因此它是不可构造的。它所有的方法都是静态的,类似于 Math
对象
目前共 13 个静态方法,可以分为函数相关、原型相关、对象相关三大类
函数相关
apply
通过指定的 this
和参数列表发起对目标 (target) 函数的调用
Reflect.apply(target, thisArg, argumentsList)
- 参数:
target
:目标函数thisArg
:target
函数调用时绑定的this
对象argumentsList
:类数组,target
函数调用时的参数列表
- 返回值:调用完带着指定参数和
this
值的给定的函数后返回的结果 - 异常:如果
target
对象不可调用,抛出TypeError
异常
示例:
function greet(name, age) {
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}
const args = ['John', 30];
Reflect.apply(greet, null, args);
// 类似于:
// greet(...args)
// greet.apply(null, args)
// Function.prototype.apply.call(greet, null, args)
construct
该方法的行为有点像 [[007.原型与原型链#^new|new
操作符]] 构造函数,相当于运行 new target(...args)
Reflect.construct(target, argumentsList[, newTarget])
- 参数:
target
:被运行的目标构造函数argumentsList
:类数组,target
构造函数调用时的参数newTarget
(可选):作为新创建对象的原型对象的constructor
属性,参考new.target
操作符,默认值为target
- 返回值:以
target
(如果newTarget
存在,则为newTarget
)函数为构造函数,argumentList
为其初始化参数的对象实例 - 异常:如果
target
或者newTarget
不是构造函数,抛出TypeError
异常
示例:
function Person(name, age) {
this.name = name;
this.age = age;
}
const args = ['John', 30];
const instance = Reflect.construct(Person, args);
// 类似于:
// new Person(...args)
Reflect.construct()
Vs. Object.create()
:
function OneClass() {
this.name = "one";
}
function OtherClass() {
this.name = "other";
}
// 创建一个对象:
var obj1 = Reflect.construct(OneClass, args, OtherClass);
// 与上述方法等效:
var obj2 = Object.create(OtherClass.prototype);
OneClass.apply(obj2, args);
console.log(obj1.name); // 'one'
console.log(obj2.name); // 'one'
console.log(obj1 instanceof OneClass); // false
console.log(obj2 instanceof OneClass); // false
console.log(obj1 instanceof OtherClass); // true
console.log(obj2 instanceof OtherClass); // true
虽然两种方式结果相同,但在创建对象过程中仍一点不同:
- 当使用
Object.create()
和Function.prototype.apply()
时,如果不使用new
操作符调用构造函数,构造函数内部的new.target
值会指向undefined
- 当调用
Reflect.construct()
来创建对象,new.target
值会自动指定到target
(或者newTarget
,前提是newTarget
指定了)
function OneClass() {
console.log("OneClass");
console.log(new.target);
}
function OtherClass() {
console.log("OtherClass");
console.log(new.target);
}
var obj1 = Reflect.construct(OneClass, args);
// 输出:
// OneClass
// function OneClass { ... }
var obj2 = Reflect.construct(OneClass, args, OtherClass);
// 输出:
// OneClass
// function OtherClass { ... }
var obj3 = Object.create(OtherClass.prototype);
OneClass.apply(obj3, args);
// 输出:
// OneClass
// undefined
原型相关
getPrototypeOf
与 Object.getPrototypeOf()
方法几乎是一样的,都是返回指定对象的原型(即内部的 [[Prototype]]
属性的值)
Reflect.getPrototypeOf(target)
- 参数:
target
:获取原型的目标对象
- 返回值:给定对象的原型。如果给定对象没有原型,则返回
null
- 异常:如果
target
不是Object
,抛出一个TypeError
异常
示例:
const obj = { x: 1 };
const proto = Reflect.getPrototypeOf(obj); // 相当于 Object.getPrototypeOf(obj)
console.log(proto === Object.prototype); // true
setPrototypeOf
除了返回类型以外,Reflect.setPrototypeOf()
与 Object.setPrototypeOf()
方法是一样的。它可设置对象的原型(即内部的 [[Prototype]]
属性)为另一个对象或 null
,如果操作成功返回 true
,否则返回 false
Reflect.setPrototypeOf(target, prototype)
- 参数:
target
:设置原型的目标对象prototype
:target
对象的新原型(一个对象或null
)
- 返回值:返回一个
Boolean
值表明是否原型已经成功设置 - 异常:如果
target
不是对象,或prototype
既不是对象也不是null
,抛出一个TypeError
异常
示例:
const obj = { x: 1 };
Reflect.setPrototypeOf(obj, Array.prototype); // 设置 obj 的原型为 Array.prototype
console.log(obj instanceof Array); // true
对象相关
get
用于获取属性值,与从对象中读取属性类似 (target[propertyKey]
),但 receiver
参数可以改变 getter
的 this
对象
Reflect.get(target, propertyKey[, receiver])
- 参数:
target
:需要取值的目标对象propertyKey
:需要获取的值的键值receiver
(可选):receiver
为getter
调用时的this
值
- 返回值:属性的值
- 异常:如果
target
不是Object
,抛出一个TypeError
异常
set
用于设置属性值,等同于 target[propertyKey] = value
,但 receiver
参数可以改变 setter
的 this
对象
Reflect.set(target, propertyKey, value[, receiver])
- 参数:
target
:设置属性的目标对象propertyKey
:需要设置的值的键值value
:设置的值receiver
(可选):receiver
为setter
调用时的this
值
- 返回值:一个
Boolean
值表明是否成功设置属性 - 异常:如果
target
不是Object
,抛出一个TypeError
异常
has
基本等同于 propertyKey in target
,用于检查一个属性是否在某个对象中
Reflect.has(target, propertyKey)
- 参数:
target
:目标对象propertyKey
:属性名,需要检查目标对象是否存在此属性
- 返回值:一个
Boolean
值指示是否存在此属性 - 异常:如果
target
不是Object
,抛出一个TypeError
异常
deleteProperty
用于删除属性,类似于 delete
操作符
Reflect.deleteProperty(target, propertyKey)
- 参数:
target
:目标对象propertyKey
:属性名
- 返回值:一个
Boolean
值表明该属性是否被成功删除 - 异常:如果
target
不是Object
,抛出一个TypeError
异常
示例:
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
defineProperty
基本等同于 Object.defineProperty()
,但返回值略有不同:
Object.defineProperty
返回一个对象,或者如果属性没有被成功定义,抛出一个TypeError
Reflect.defineProperty
方法只返回一个Boolean
,来说明该属性是否被成功定义
Reflect.defineProperty(target, propertyKey, attributes)
- 参数:
target
:目标对象propertyKey
:要定义或修改的属性的名称attributes
:要定义或修改的属性的描述
- 返回值:
Boolean
值指示了属性是否被成功定义 - 异常:如果
target
不是Object
,抛出一个TypeError
异常
getOwnPropertyDescriptor
用于获取对象自身的某个属性的属性描述符,与 Object.getOwnPropertyDescriptor()
方法相似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined
Reflect.getOwnPropertyDescriptor(target, propertyKey)
- 参数:
target
:需要寻找属性的目标对象propertyKey
:获取属性描述符的属性名
- 返回值:如果属性存在于给定的目标对象中,则返回属性描述符;否则,返回
undefined
- 异常:如果
target
不是Object
,抛出一个TypeError
异常
ownKeys
返回一个由目标对象自身的属性键组成的数组,等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Reflect.ownKeys(target)
- 参数:
target
:目标对象
- 返回值:由目标对象的自身属性键组成的
Array
- 异常:如果
target
不是Object
,抛出一个TypeError
异常
preventExtensions
使一个对象变为不可扩展(阻止新属性添加到对象),基本等同于 Object.preventExtensions()
,但有一些不同:
Reflect.preventExtensions()
的第一个参数不是一个对象,将抛出一个TypeError
异常Object.preventExtensions()
非对象的第一个参数会被强制转换为一个对象
Reflect.preventExtensions(target)
- 参数:
target
:目标对象
- 返回值:一个
Boolean
值表明目标对象是否成功被设置为不可扩展 - 异常:如果
target
不是Object
,抛出一个TypeError
异常
isExtensible
判断一个对象是否可扩展(即是否能够添加新的属性)。与 Object.isExtensible()
方法相似,但有一些不同:
Reflect.isExtensible()
的第一个参数不是一个对象,将抛出一个TypeError
异常Object.isExtensible()
非对象的第一个参数会被强制转换为一个对象
Reflect.isExtensible(target)
- 参数:
target
:目标对象
- 返回值:返回一个
Boolean
值表明该对象是否可扩展 - 异常:如果
target
不是Object
,抛出一个TypeError
异常
Proxy
为什么 Proxy 里一定要使用 Reflect?
不使用 Reflect:
const obj = {
_name: 'test',
get name() {
return this._name;
}
};
const proxyObj = new Proxy(obj, {
get(target, property, receiver) {
return target[property];
},
});
const child = {
_name: 'child'
};
Reflect.setPrototypeOf(child, proxyObj);
child.name // test,期望为 child
proxyObj.name // test
因为代理对象的 get
拦截中固定返回的 target[property]
,target
永远指向 obj
,所以拿到的永远是 obj
的 _name
属性值
const obj = {
_name: 'test',
get name(){
return this._name;
}
};
const proxyObj = new Proxy(obj,{
get(target, property, receiver){
return Reflect.get(target, property, receiver)
},
});
const child = {
_name: 'child'
};
Reflect.setPrototypeOf(child, proxyObj);
child.name // child
proxyObj.name // test
当使用 Reflect
时可以正确转发运行时上下文,其实主要就是 receiver
这个参数,receiver
代表的是代理对象本身或者继承自代理对象的对象,它表示触发 get
时正确的上下文