Reflect

Overview

The Reflect object, like the Proxy object, is also a new API provided by ES6 for manipulating objects. The design goals of the Reflect object are as follows.

(1) Put some methods (such as Object.defineProperty) of the Object object that are obviously internal to the language on the Reflect object. At this stage, some methods are deployed on both Object and Reflect objects, and future new methods will only be deployed on Reflect objects. In other words, the internal methods of the language can be obtained from the Reflect object.

(2) Modify the return results of some Object methods to make them more reasonable. For example, Object.defineProperty(obj, name, desc) will throw an error when the property cannot be defined, while Reflect.defineProperty(obj, name, desc) will return false.

// Old way of writing
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

// new wording
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3) Let Object operations become functional behaviors. Certain Object operations are imperative, such as name in obj and delete obj[name], while Reflect.has(obj, name) and Reflect.deleteProperty(obj, name) make them change Become a functional behavior.

// Old way of writing
"assign" in Object; // true

// new wording
Reflect.has(Object, "assign"); // true

(4) There is a one-to-one correspondence between the methods of the Reflect object and the methods of the Proxy object. As long as it is a method of the Proxy object, the corresponding method can be found on the Reflect object. This allows the Proxy object to conveniently call the corresponding Reflect method to complete the default behavior as the basis for modifying the behavior. In other words, no matter how Proxy modifies the default behavior, you can always get the default behavior on Reflect.

Proxy(target, {
  set: function (target, name, value, receiver) {
    var success = Reflect.set(target, name, value, receiver);
    if (success) {
      console.log("property " + name + " on " + target + " set to " + value);
    }
    return success;
  },
});

In the above code, the Proxy method intercepts the property assignment behavior of the target object. It uses the Reflect.set method to assign values ​​to the properties of the object to ensure that the original behavior is completed before deploying additional functions.

Here is another example.

var loggedObj = new Proxy(obj, {
  get(target, name) {
    console.log("get", target, name);
    return Reflect.get(target, name);
  },
  deleteProperty(target, name) {
    console.log("delete" + name);
    return Reflect.deleteProperty(target, name);
  },
  has(target, name) {
    console.log("has" + name);
    return Reflect.has(target, name);
  },
});

In the above code, for each interception operation (get, delete, has) of the Proxy object, the corresponding Reflect method is called internally to ensure that the original behavior can be executed normally. The added work is to output a line of log for each operation.

With the Reflect object, many operations will be easier to read.

// Old way of writing
Function.prototype.apply.call(Math.floor, undefined, [1.75]); // 1

// new wording
Reflect.apply(Math.floor, undefined, [1.75]); // 1

Static method

There are 13 static methods in the Reflect object.

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

Most of the functions of the above methods are the same as those of the method of the same name of the Object object, and they have a one-to-one correspondence with the methods of the Proxy object. The following is an explanation of them.

Reflect.get(target, name, receiver)

The Reflect.get method finds and returns the name property of the target object. If there is no such property, it returns undefined.

var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

Reflect.get(myObject, "foo"); // 1
Reflect.get(myObject, "bar"); // 2
Reflect.get(myObject, "baz"); // 3

If a getter is deployed in the name property, then the this of the read function is bound to the receiver.

var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

var myReceiverObject = {
  foo: 4,
  bar: 4,
};

Reflect.get(myObject, "baz", myReceiverObject); // 8

If the first parameter is not an object, the Reflect.get method will report an error.

Reflect.get(1, "foo"); // error
Reflect.get(false, "foo"); // error

Reflect.set(target, name, value, receiver)

The Reflect.set method sets the name property of the target object equal to value.

var myObject = {
  foo: 1,
  set bar(value) {
    return (this.foo = value);
  },
};

myObject.foo; // 1

Reflect.set(myObject, "foo", 2);
myObject.foo; // 2

Reflect.set(myObject, "bar", 3);
myObject.foo; // 3

If the assignment function is set in the name attribute, then the this of the assignment function is bound to the receiver.

var myObject = {
  foo: 4,
  set bar(value) {
    return (this.foo = value);
  },
};

var myReceiverObject = {
  foo: 0,
};

Reflect.set(myObject, "bar", 1, myReceiverObject);
myObject.foo; // 4
myReceiverObject.foo; // 1

Note that if the Proxy object is used in conjunction with the Reflect object, the former intercepts the assignment operation, the latter completes the default behavior of the assignment, and passes in the receiver, then Reflect.set will trigger the interception of Proxy.defineProperty .

let p = {
  a: "a",
};

let handler = {
  set(target, key, value, receiver) {
    console.log("set");
    Reflect.set(target, key, value, receiver);
  },
  defineProperty(target, key, attribute) {
    console.log("defineProperty");
    Reflect.defineProperty(target, key, attribute);
  },
};

let obj = new Proxy(p, handler);
obj.a = "A";
// set
// defineProperty

In the above code, Reflect.set is used in Proxy.set interception, and receiver is passed in, which triggers the interception of Proxy.defineProperty. This is because the receiver parameter of Proxy.set always points to the current Proxy instance (that is, the obj in the above example), and once Reflect.set is passed to receiver, the property will be assigned Go to the receiver (ie obj), causing the defineProperty interception to be triggered. If Reflect.set does not pass in receiver, then defineProperty interception will not be triggered.

let p = {
  a: "a",
};

let handler = {
  set(target, key, value, receiver) {
    console.log("set");
    Reflect.set(target, key, value);
  },
  defineProperty(target, key, attribute) {
    console.log("defineProperty");
    Reflect.defineProperty(target, key, attribute);
  },
};

let obj = new Proxy(p, handler);
obj.a = "A";
// set

If the first parameter is not an object, Reflect.set will report an error.

Reflect.set(1,'foo', ()) // report an error
Reflect.set(false,'foo', ()) // report an error

Reflect.has(obj, name)

The Reflect.has method corresponds to the in operator in name in obj.

var myObject = {
  foo: 1,
};

// old way
"foo" in myObject; // true

// new wording
Reflect.has(myObject, "foo"); // true

If the first parameter of the Reflect.has() method is not an object, an error will be reported.

Reflect.deleteProperty(obj, name)

The Reflect.deleteProperty method is equivalent to delete obj[name], which is used to delete object properties.

const myObj = { foo: "bar" };

// old way
delete myObj.foo;

// new wording
Reflect.deleteProperty(myObj, "foo");

This method returns a boolean value. If the deletion is successful, or the deleted attribute does not exist, true is returned; if the deletion fails, the deleted attribute still exists, and false is returned.

If the first parameter of the Reflect.deleteProperty() method is not an object, an error will be reported.

Reflect.construct(target, args)

The Reflect.construct method is equivalent to new target(...args), which provides a way to call the constructor without using new.

function Greeting(name) {
  this.name = name;
}

// How to write new
const instance = new Greeting("Zhang San");

// How to write Reflect.construct
const instance = Reflect.construct(Greeting, ["张三"]);

If the first parameter of the Reflect.construct() method is not a function, an error will be reported.

Reflect.getPrototypeOf(obj)

The Reflect.getPrototypeOf method is used to read the __proto__ property of an object, corresponding to Object.getPrototypeOf(obj).

const myObj = new FancyThing();

// old way
Object.getPrototypeOf(myObj) === FancyThing.prototype;

// new wording
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;

One difference between Reflect.getPrototypeOf and Object.getPrototypeOf is that if the parameter is not an object, Object.getPrototypeOf will convert the parameter to an object, and then run, and Reflect.getPrototypeOf will report an error.

Object.getPrototypeOf(1); // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1); // error

Reflect.setPrototypeOf(obj, newProto)

The Reflect.setPrototypeOf method is used to set the prototype of the target object, corresponding to the Object.setPrototypeOf(obj, newProto) method. It returns a Boolean value indicating whether the setting is successful.

const myObj = {};

// old way
Object.setPrototypeOf(myObj, Array.prototype);

// new wording
Reflect.setPrototypeOf(myObj, Array.prototype);

myObj.length; // 0

If the prototype of the target object cannot be set (for example, the target object cannot be expanded), the Reflect.setPrototypeOf method returns false.

Reflect.setPrototypeOf({}, null);
// true
Reflect.setPrototypeOf(Object.freeze({}), null);
// false

If the first parameter is not an object, Object.setPrototypeOf will return the first parameter itself, and Reflect.setPrototypeOf will report an error.

Object.setPrototypeOf(1, {});
// 1

Reflect.setPrototypeOf(1, {});
// TypeError: Reflect.setPrototypeOf called on non-object

If the first parameter is undefined or null, both Object.setPrototypeOf and Reflect.setPrototypeOf will report errors.

Object.setPrototypeOf(null, {});
// TypeError: Object.setPrototypeOf called on null or undefined

Reflect.setPrototypeOf(null, {});
// TypeError: Reflect.setPrototypeOf called on non-object

Reflect.apply(func, thisArg, args)

The Reflect.apply method is equivalent to Function.prototype.apply.call(func, thisArg, args), which is used to bind the this object and execute the given function.

Generally speaking, if you want to bind the this object of a function, you can write fn.apply(obj, args), but if the function defines its own apply method, it can only be written as Function.prototype .apply.call(fn, obj, args), using the Reflect object can simplify this operation.

const ages = [11, 33, 12, 54, 18, 96];

// old way
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);

// new wording
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);

Reflect.defineProperty(target, propertyKey, attributes)

The Reflect.defineProperty method is basically equivalent to Object.defineProperty, which is used to define properties for objects. In the future, the latter will be gradually abolished, please use Reflect.defineProperty instead from now on.

function MyDate() {
  /*…*/
}

// old way
Object.defineProperty(MyDate, "now", {
  value: () => Date.now(),
});

// new wording
Reflect.defineProperty(MyDate, "now", {
  value: () => Date.now(),
});

If the first parameter of Reflect.defineProperty is not an object, an error will be thrown, such as Reflect.defineProperty(1,'foo').

This method can be used in conjunction with Proxy.defineProperty.

const p = new Proxy(
  {},
  {
    defineProperty(target, prop, descriptor) {
      console.log(descriptor);
      return Reflect.defineProperty(target, prop, descriptor);
    },
  }
);

p.foo = "bar";
// {value: "bar", writable: true, enumerable: true, configurable: true}

p.foo; // "bar"

In the above code, Proxy.defineProperty sets the interception for property assignment, and then uses Reflect.defineProperty to complete the assignment.

Reflect.getOwnPropertyDescriptor(target, propertyKey)

Reflect.getOwnPropertyDescriptor is basically equivalent to Object.getOwnPropertyDescriptor, which is used to get the description object of the specified property, which will replace the latter in the future.

var myObject = {};
Object.defineProperty(myObject, "hidden", {
  value: true,
  enumerable: false,
});

// old way
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, "hidden");

// new wording
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, "hidden");

One difference between Reflect.getOwnPropertyDescriptor and Object.getOwnPropertyDescriptor is that if the first parameter is not an object, Object.getOwnPropertyDescriptor(1,'foo') does not report an error and returns undefined, while Reflect.getOwnPropertyDescriptor( 1,'foo') will throw an error, indicating that the parameter is illegal.

Reflect.isExtensible (target)

The Reflect.isExtensible method corresponds to Object.isExtensible and returns a boolean value indicating whether the current object is extensible.

const myObject = {};

// old way
Object.isExtensible(myObject); // true

// new wording
Reflect.isExtensible(myObject); // true

If the parameter is not an object, Object.isExtensible will return false, because non-objects are inherently non-extensible, and Reflect.isExtensible will report an error.

Object.isExtensible(1); // false
Reflect.isExtensible(1); // error

Reflect.preventExtensions(target)

Reflect.preventExtensions corresponds to the Object.preventExtensions method, which is used to make an object non-extensible. It returns a Boolean value indicating whether the operation was successful.

var myObject = {};

// old way
Object.preventExtensions(myObject); // Object {}

// new wording
Reflect.preventExtensions(myObject); // true

If the parameter is not an object, Object.preventExtensions will report an error in ES5 environment, and return the passed parameter in ES6 environment, and Reflect.preventExtensions will report an error.

// ES5 environment
Object.preventExtensions(1); // error

// ES6 environment
Object.preventExtensions(1); // 1

// new wording
Reflect.preventExtensions(1); // error

Reflect.ownKeys (target)

The Reflect.ownKeys method is used to return all the properties of the object, basically equivalent to the sum of Object.getOwnPropertyNames and Object.getOwnPropertySymbols.

var myObject = {
  foo: 1,
  bar: 2,
  [Symbol.for("baz")]: 3,
  [Symbol.for("bing")]: 4,
};

// old way
Object.getOwnPropertyNames(myObject);
// ['foo', 'bar']

Object.getOwnPropertySymbols(myObject);
// [Symbol (baz), Symbol (bing)]

// new wording
Reflect.ownKeys(myObject);
// ['foo', 'bar', Symbol (baz), Symbol (bing)]

If the first parameter of the Reflect.ownKeys() method is not an object, an error will be reported.

Example: Using Proxy to implement observer mode

Observer mode refers to the function that automatically observes the data object. Once the object changes, the function will be executed automatically.

const person = observable({
  name: "Zhang San",
  age: 20,
});

function print() {
  console.log(`${person.name}, ${person.age}`);
}

observe(print);
person.name = "Li Si";
// output
// Li Si, 20

In the above code, the data object person is the observation target, and the function print is the observer. Once the data object changes, print will be executed automatically.

Next, use Proxy to write the simplest implementation of the observer pattern, which implements the two functions observable and observe. The idea is that the observable function returns a proxy proxy of the original object, intercepts assignment operations, and triggers various functions that act as observers.

const queuedObservers = new Set();

const observe = (fn) => queuedObservers.add(fn);
const observable = (obj) => new Proxy(obj, { set });

function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach((observer) => observer());
  return result;
}

In the above code, a Set set is defined first, and all observer functions are put into this set. Then, the observable function returns the proxy of the original object, intercepting the assignment operation. In the intercept function set, all observers are automatically executed.