Reflect
# Reflect
# Overview
Like the Proxy object, the Reflect object is a new API provided by ES6 for manipulating objects. The design purposes of the Reflect object include the following.
(1) Move methods that clearly belong to the language's internals from the Object object to the Reflect object (such as Object.defineProperty). Currently, some methods are deployed on both Object and Reflect, but future new methods will only be deployed on Reflect. In other words, you can access internal language methods from the Reflect object.
(2) Modify the return values of certain Object methods to make them more reasonable. For example, Object.defineProperty(obj, name, desc) throws an error when it cannot define a property, while Reflect.defineProperty(obj, name, desc) returns false.
// Old approach
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// New approach
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
2
3
4
5
6
7
8
9
10
11
12
13
14
(3) Make all Object operations functional. Some Object operations are imperative, such as name in obj and delete obj[name], while Reflect.has(obj, name) and Reflect.deleteProperty(obj, name) turn them into functional behavior.
// Old approach
'assign' in Object // true
// New approach
Reflect.has(Object, 'assign') // true
2
3
4
5
(4) The methods of the Reflect object correspond one-to-one with those of the Proxy object. For every Proxy method, there is a corresponding method on Reflect. This allows Proxy objects to conveniently call the corresponding Reflect methods to perform default behavior as the basis for modified behavior. In other words, no matter how Proxy modifies the default behavior, you can always get the default behavior from 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;
}
});
2
3
4
5
6
7
8
9
In the code above, the Proxy method intercepts the property assignment behavior of the target object. It uses Reflect.set to assign the value to the object's property, ensuring the original behavior is completed, and then adds additional functionality.
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);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
In the code above, each Proxy interception operation (get, delete, has) internally calls the corresponding Reflect method, ensuring native behavior executes normally. The added work is to output a log line for each operation.
With the Reflect object, many operations become more readable.
// Old approach
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// New approach
Reflect.apply(Math.floor, undefined, [1.75]) // 1
2
3
4
5
# Static Methods
The Reflect object has 13 static methods in total.
- 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 these methods have the same function as the same-named methods on the Object object, and they correspond one-to-one with Proxy methods. Below are explanations for each.
# Reflect.get(target, name, receiver)
The Reflect.get method looks up and returns the name property of the target object. If the property doesn't exist, 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
2
3
4
5
6
7
8
9
10
11
If the name property has a getter deployed, the getter's this is bound to 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
2
3
4
5
6
7
8
9
10
11
12
13
14
If the first argument is not an object, Reflect.get will throw an error.
Reflect.get(1, 'foo') // Error
Reflect.get(false, 'foo') // Error
2
# Reflect.set(target, name, value, receiver)
The Reflect.set method sets the name property of the target object 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
2
3
4
5
6
7
8
9
10
11
12
13
14
If the name property has a setter deployed, the setter's this is bound to 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
2
3
4
5
6
7
8
9
10
11
12
13
14
Note that if Proxy and Reflect are used together, where the former intercepts assignment and the latter performs the default assignment behavior, and receiver is passed, then Reflect.set will trigger Proxy.defineProperty interception.
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In the code above, Proxy.set uses Reflect.set internally and passes receiver, which triggers Proxy.defineProperty. This is because Proxy.set's receiver parameter always points to the current Proxy instance (i.e., obj in the example), and once Reflect.set receives receiver, it assigns the property to receiver (i.e., obj), which triggers defineProperty. If Reflect.set does not receive receiver, then defineProperty 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
If the first argument is not an object, Reflect.set will throw an error.
Reflect.set(1, 'foo', {}) // Error
Reflect.set(false, 'foo', {}) // Error
2
# Reflect.has(obj, name)
The Reflect.has method corresponds to the in operator in name in obj.
var myObject = {
foo: 1,
};
// Old approach
'foo' in myObject // true
// New approach
Reflect.has(myObject, 'foo') // true
2
3
4
5
6
7
8
9
If the first argument of Reflect.has() is not an object, it will throw an error.
# Reflect.deleteProperty(obj, name)
The Reflect.deleteProperty method is equivalent to delete obj[name] and is used to delete an object's property.
const myObj = { foo: 'bar' };
// Old approach
delete myObj.foo;
// New approach
Reflect.deleteProperty(myObj, 'foo');
2
3
4
5
6
7
This method returns a boolean. If the deletion succeeds or if the deleted property doesn't exist, it returns true; if deletion fails and the property still exists, it returns false.
If the first argument of Reflect.deleteProperty() is not an object, it will throw an error.
# Reflect.construct(target, args)
The Reflect.construct method is equivalent to new target(...args), providing a way to call constructors without using new.
function Greeting(name) {
this.name = name;
}
// Using new
const instance = new Greeting('张三');
// Using Reflect.construct
const instance = Reflect.construct(Greeting, ['张三']);
2
3
4
5
6
7
8
9
If the first argument of Reflect.construct() is not a function, it will throw an error.
# Reflect.getPrototypeOf(obj)
The Reflect.getPrototypeOf method is used to read an object's __proto__ property, corresponding to Object.getPrototypeOf(obj).
const myObj = new FancyThing();
// Old approach
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// New approach
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;
2
3
4
5
6
7
One difference between Reflect.getPrototypeOf and Object.getPrototypeOf is that if the argument is not an object, Object.getPrototypeOf will convert the argument to an object before running, while Reflect.getPrototypeOf will throw an error.
Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // Error
2
# Reflect.setPrototypeOf(obj, newProto)
The Reflect.setPrototypeOf method is used to set the target object's prototype, corresponding to Object.setPrototypeOf(obj, newProto). It returns a boolean indicating whether the operation was successful.
const myObj = {};
// Old approach
Object.setPrototypeOf(myObj, Array.prototype);
// New approach
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length // 0
2
3
4
5
6
7
8
9
If the target object's prototype cannot be set (e.g., the target object is frozen), Reflect.setPrototypeOf returns false.
Reflect.setPrototypeOf({}, null)
// true
Reflect.setPrototypeOf(Object.freeze({}), null)
// false
2
3
4
If the first argument is not an object, Object.setPrototypeOf returns the first argument itself, while Reflect.setPrototypeOf throws an error.
Object.setPrototypeOf(1, {})
// 1
Reflect.setPrototypeOf(1, {})
// TypeError: Reflect.setPrototypeOf called on non-object
2
3
4
5
If the first argument is undefined or null, both Object.setPrototypeOf and Reflect.setPrototypeOf will throw an error.
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Reflect.setPrototypeOf(null, {})
// TypeError: Reflect.setPrototypeOf called on non-object
2
3
4
5
# Reflect.apply(func, thisArg, args)
The Reflect.apply method is equivalent to Function.prototype.apply.call(func, thisArg, args) and is used to execute a given function after binding a this object.
Generally, to bind this for a function, you write fn.apply(obj, args). But if a function defines its own apply method, you have to write Function.prototype.apply.call(fn, obj, args). Using Reflect simplifies this operation.
const ages = [11, 33, 12, 54, 18, 96];
// Old approach
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// New approach
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);
2
3
4
5
6
7
8
9
10
11
# Reflect.defineProperty(target, propertyKey, attributes)
The Reflect.defineProperty method is basically equivalent to Object.defineProperty and is used to define properties for objects. In the future, the latter will be gradually deprecated, so start using Reflect.defineProperty from now on.
function MyDate() {
/*…*/
}
// Old approach
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
// New approach
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
2
3
4
5
6
7
8
9
10
11
12
13
If the first argument to Reflect.defineProperty is not an object, it will throw an error, e.g., Reflect.defineProperty(1, 'foo').
This method can be used together 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"
2
3
4
5
6
7
8
9
10
11
In the code above, Proxy.defineProperty intercepts property assignment, then uses Reflect.defineProperty to complete the assignment.
# Reflect.getOwnPropertyDescriptor(target, propertyKey)
Reflect.getOwnPropertyDescriptor is basically equivalent to Object.getOwnPropertyDescriptor and is used to get the descriptor of a specified property. It will replace the latter in the future.
var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
});
// Old approach
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
// New approach
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
2
3
4
5
6
7
8
9
10
11
One difference between Reflect.getOwnPropertyDescriptor and Object.getOwnPropertyDescriptor is that if the first argument is not an object, Object.getOwnPropertyDescriptor(1, 'foo') doesn't throw an error but returns undefined, while Reflect.getOwnPropertyDescriptor(1, 'foo') throws an error indicating an illegal argument.
# Reflect.isExtensible (target)
The Reflect.isExtensible method corresponds to Object.isExtensible and returns a boolean indicating whether the current object is extensible.
const myObject = {};
// Old approach
Object.isExtensible(myObject) // true
// New approach
Reflect.isExtensible(myObject) // true
2
3
4
5
6
7
If the argument is not an object, Object.isExtensible returns false because non-objects are inherently non-extensible, while Reflect.isExtensible throws an error.
Object.isExtensible(1) // false
Reflect.isExtensible(1) // Error
2
# Reflect.preventExtensions(target)
Reflect.preventExtensions corresponds to Object.preventExtensions and is used to make an object non-extensible. It returns a boolean indicating whether the operation was successful.
var myObject = {};
// Old approach
Object.preventExtensions(myObject) // Object {}
// New approach
Reflect.preventExtensions(myObject) // true
2
3
4
5
6
7
If the argument is not an object, Object.preventExtensions throws an error in ES5, returns the argument in ES6, while Reflect.preventExtensions throws an error.
// ES5 environment
Object.preventExtensions(1) // Error
// ES6 environment
Object.preventExtensions(1) // 1
// New approach
Reflect.preventExtensions(1) // Error
2
3
4
5
6
7
8
# Reflect.ownKeys (target)
The Reflect.ownKeys method returns all properties of an object, essentially combining Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
// Old approach
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']
Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]
// New approach
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
If the first argument of Reflect.ownKeys() is not an object, it will throw an error.
# Example: Implementing the Observer Pattern with Proxy
The Observer pattern means that a function automatically observes a data object, and whenever the object changes, the function executes automatically.
const person = observable({
name: '张三',
age: 20
}); // Observable target
function print() {
console.log(`${person.name}, ${person.age}`)
} // Observer
observe(print); // Start observing
person.name = '李四';
// Output
// 李四, 20
2
3
4
5
6
7
8
9
10
11
12
13
In the code above, the data object person is the observable target, and the function print is the observer. Whenever the data object changes, print executes automatically.
Below, we use Proxy to write the simplest implementation of the observer pattern -- implementing the observable and observe functions. The idea is that observable returns a Proxy of the original object that intercepts assignment operations and triggers all observer functions.
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;
}
2
3
4
5
6
7
8
9
10
In the code above, a Set collection is defined first, and all observer functions are placed in this collection. Then, observable returns a proxy of the original object that intercepts assignment operations. The set interceptor function automatically executes all observers.