元编程 (Meta Programming) 是指编写可以操作或改变其他程序的程序。元编程可以改变 JavaScript 的一些基本操作的行为

主要与这三个对象有关:

什么是基本操作

基本操作包括:属性访问和赋值、属性删除、属性枚举、函数调用、对象构造等

属性访问 (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)

示例:

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])

示例:

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

虽然两种方式结果相同,但在创建对象过程中仍一点不同:

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)

示例:

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)

示例:

const obj = { x: 1 };
Reflect.setPrototypeOf(obj, Array.prototype); // 设置 obj 的原型为 Array.prototype
console.log(obj instanceof Array); // true

对象相关

get

用于获取属性值,与从对象中读取属性类似 (target[propertyKey]),但 receiver 参数可以改变 getterthis 对象

Reflect.get(target, propertyKey[, receiver])

set

用于设置属性值,等同于 target[propertyKey] = value,但 receiver 参数可以改变 setterthis 对象

Reflect.set(target, propertyKey, value[, receiver])

has

基本等同于 propertyKey in target,用于检查一个属性是否在某个对象中

Reflect.has(target, propertyKey)

deleteProperty

用于删除属性,类似于 delete 操作符

Reflect.deleteProperty(target, propertyKey)

示例:

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(),但返回值略有不同:

Reflect.defineProperty(target, propertyKey, attributes)

getOwnPropertyDescriptor

用于获取对象自身的某个属性的属性描述符,与 Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined

Reflect.getOwnPropertyDescriptor(target, propertyKey)

ownKeys

返回一个由目标对象自身的属性键组成的数组,等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))

Reflect.ownKeys(target)

preventExtensions

使一个对象变为不可扩展(阻止新属性添加到对象),基本等同于 Object.preventExtensions(),但有一些不同:

Reflect.preventExtensions(target)

isExtensible

判断一个对象是否可扩展(即是否能够添加新的属性)。与 Object.isExtensible() 方法相似,但有一些不同:

Reflect.isExtensible(target)

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 时正确的上下文