What is reflection
The concept of reflection exists in many programming languages, such as Java and C#.
In object-oriented programming, the class and method are generally defined first, and then the object is created to explicitly call the method, such as the following example:
public class User{
private String name;
private Date birthday;
//....
public int calculateAgeByBirthday(){
// .....
}
}
// 调用
User u = new User("jack", new Date());
u.calculateAgeByBirthday();
We are familiar with the above call methods, but when you want to write some abstract frameworks (the frameworks need to interoperate with business-defined classes), because you don’t know the members and methods of the business class, then reflection dynamically obtains member variables Or call the method.
In the following example, we use reflection to convert json into a Java object.
public static class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 使用反射调用对象setter方法。
public static <T> T fill(Class<T> userClass, Map<String, Object> json) throws Exception {
Field[] fields = userClass.getDeclaredFields();
T user = userClass.newInstance();
for (Field field : fields) {
// 首字母大写
String name = field.getName();
char[] arr = name.toCharArray();
arr[0] = Character.toUpperCase(arr[0]);
System.out.println(new String(arr));
Method method = userClass.getDeclaredMethod("set" + new String(arr), field.getType());
Object returnValue = method.invoke(user, json.get(name));
}
return user;
}
Reflect in JavaScript
JavaScript provides the reflection built-in object Reflect
in ES6, but the reflection in JavaScript is different from Java reflection. Reflect
look at the 13 static methods provided by 061750b8446be1.
- 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)
Reflect.get(target, name, receiver)
Reflect.get
method finds and returns the name
target
object. If there is no such attribute, it returns undefined
.
const obj = {
name: 'jack',
age: 12,
get userInfo() {
return this.name + ' age is ' + this.age;
}
}
Reflect.get(obj, 'name') // jack
Reflect.get(obj, 'age') // 12
Reflect.get(obj, 'userInfo') // jack age is 12
// 如果传递了receiver参数,在调用userInfo()函数时,this是指向receiver对象。
const receiverObj = {
name: '小明',
age: 22
};
Reflect.get(obj, 'userInfo', receiverObj) // 小明 age is 22
Reflect.set(target, name, value, receiver)
const obj = {
name: 'jack',
age: 12,
set updateAge(value) {
return this.age = value;
},
}
Reflect.set(obj, 'age', 22);
obj.age // 22
// 如果传递了receiver参数,在调用updateAge()函数时,this是指向receiver对象。
const receiverObj = {
age: 0
};
Reflect.set(obj, 'updateAge', 10, receiverObj) //
obj.age // 22
receiverObj.age // 10
Reflect.has(obj, name)
Reflect.has
method is equivalent name in obj
inside in
operator.
const obj = {
name: 'jack',
}
obj in name // true
Reflect.has(obj, 'name') // true
Reflect.deleteProperty(obj, name)
Reflect.deleteProperty
method is equivalent to delete obj[name]
and is used to delete the attributes of the object. If the deletion is successful, or the deleted attribute does not exist, the return is true
; if the deletion fails, the deleted attribute still exists, and the return is false
.
const obj = {
name: 'jack',
}
delete obj.name
Reflect.deleteProperty(obj, 'name')
Reflect.construct(target, args)
Reflect.construct
method is equivalent to new target(...args)
.
function User(name){
this.name = name;
}
const user = new User('jack');
Reflect.construct(User, ['jack']);
Reflect.getPrototypeOf(obj)
Reflect.getPrototypeOf
method is used to read the __proto__
attribute of the object.
Reflect.setPrototypeOf(obj, newProto)
Reflect.setPrototypeOf
method is used to set the prototype of the target object. Returns a boolean value indicating whether the setting is successful.
const obj = {
name: 'jack',
}
Reflect.setPrototypeOf(obj, Array.prototype);
obj.length // 0
Reflect.apply(func, thisArg, args)
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.
const nums = [1,2,3,4,5];
const min = Math.max.apply(Math, nums);
// 通过 Reflect.apply 调用
const min = Reflect.apply(Math.min, Math, nums);
Reflect.defineProperty(target, propertyKey, attributes)
Reflect.defineProperty
method is equivalent to Object.defineProperty
and is used to define properties for the object.
const obj = {};
Object.defineProperty(obj, 'property', {
value: 0,
writable: false
});
Reflect.defineProperty(obj, 'property', {
value: 0,
writable: false
});
Reflect.getOwnPropertyDescriptor(target, propertyKey)
Get the description object of the specified attribute.
Reflect.isExtensible (target)
Returns a boolean value indicating whether the current object is expandable.
Reflect.preventExtensions(target)
Used to make an object non-extensible. It returns a Boolean value indicating whether the operation was successful.
Reflect.ownKeys (target)
Reflect.ownKeys
method is used to return all the attributes of the object.
const obj = {
name: 'jack',
age: 12,
get userInfo() {
return this.name + ' age is ' + this.age;
}
}
Object.getOwnPropertyNames(obj)
Reflect.ownKeys(obj) // ['name', 'age', 'userInfo']
Proxy in JavaScript
The proxy is very useful in programming, it can add a layer of "intercept" before the target object to achieve some common logic.
Proxy constructor Proxy(target, handler)
parameters:
- target: The target object of the proxy, which can be any type of object, including built-in arrays, functions, and proxy objects.
- handler: It is an object whose properties provide processing functions when certain operations occur.
const user = {name: 'hello'}
const proxy = new Proxy(user, {
get: function(target, property) { // 读取属性时触发
return 'hi';
}
});
proxy.name // 'hi'
Interception operations supported in Proxy
- handler.get(target, property, receiver)
- handler.set(target, property, value, receiver)
- handler.has(target, property)
- handler.defineProperty(target, property, descriptor)
- handler.deleteProperty(target, property)
- handler.getOwnPropertyDescriptor(target, prop)
- handler.getPrototypeOf(target)
- handler.setPrototypeOf(target, prototype)
- handler.isExtensible(target)
- handler.ownKeys(target)
- handler.preventExtensions(target)
- handler.apply(target, thisArg, argumentsList)
- handler.construct(target, argumentsList, newTarget)
get()
Used to intercept the read operation of an attribute, it can accept three parameters, which are the target object, the attribute name, and the proxy instance itself. The last parameter is optional.
const user = {
name: 'jack'
}
// 只有属性存在才返回值,否则抛出异常。
const proxy = new Proxy(user, {
get: function(target, property) {
if (!(property in target)) {
throw new ReferenceError(`${property} does not exist.`);
}
return target[property];
}
});
proxy.name // jack
proxy.age // ReferenceError: age does not exist.
We can define some public proxy objects, and then let the child objects inherit.
// 只有属性存在才返回值,否则抛出异常。
const proxy = new Proxy({}, {
get: function(target, property) {
if (!(property in target)) {
throw new ReferenceError(`${property} does not exist.`);
}
return target[property];
}
});
let obj = Object.create(proxy);
obj.name = 'hello'
obj.name // hello
obj.age // ReferenceError: age does not exist.
set()
Used to intercept an attribute assignment operation, it can accept four parameters, which are the target object, the attribute name, the attribute value, and the Proxy instance itself. The last parameter is optional.
// 字符类型的属性长度校验
let sizeValidator = {
set: function(target, property, value, receiver) {
if (typeof value == 'string' && value.length > 5) {
throw new RangeError('Cannot exceed 5 character.');
}
target[property] = value;
return true;
}
};
const validator = new Proxy({}, sizeValidator);
let obj = Object.create(validator);
obj.name = '123456' // RangeError: Cannot exceed 5 character.
obj.age = 12 // 12
has()
Used to intercept the HasProperty
operation, that is, when judging whether the object has a certain attribute, this method will take effect. Such as in
operator.
It accepts two parameters, namely the target object and the property name to be queried.
const handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false
defineProperty()
defineProperty()
method intercepted the Object.defineProperty()
operation.
deleteProperty()
Used to intercept the delete
operation. If this method throws an error or returns false
, the current attribute cannot be delete
command.
getOwnPropertyDescriptor()
getOwnPropertyDescriptor()
method intercepts Object.getOwnPropertyDescriptor()
and returns an attribute description object or undefined
.
getPrototypeOf()
Mainly used to intercept and obtain object prototypes, the interception operations are as follows:
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
__proto__
- Object.prototype.isPrototypeOf()
- instanceof
const obj = {};
const proto = {};
const handler = {
getPrototypeOf(target) {
console.log(target === obj); // true
console.log(this === handler); // true
return proto;
}
};
const p = new Proxy(obj, handler);
console.log(Object.getPrototypeOf(p) === proto); // true
setPrototypeOf()
Mainly used to intercept the Object.setPrototypeOf()
method.
const handlerReturnsFalse = {
setPrototypeOf(target, newProto) {
return false;
}
};
const newProto = {}, target = {};
const p1 = new Proxy(target, handlerReturnsFalse);
Object.setPrototypeOf(p1, newProto); // throws a TypeError
Reflect.setPrototypeOf(p1, newProto); // returns false
isExtensible()
The method intercepts operation Object.isExtensible()
const p = new Proxy({}, {
isExtensible: function(target) {
console.log('called');
return true;//也可以return 1;等表示为true的值
}
});
console.log(Object.isExtensible(p)); // "called"
// true
ownKeys()
Used to intercept the read operation of the object's own properties. Specifically, intercept the following operations.
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for...in
loop.
const p = new Proxy({}, {
ownKeys: function(target) {
console.log('called');
return ['a', 'b', 'c'];
}
});
console.log(Object.getOwnPropertyNames(p)); // "called"
preventExtensions()
Used to intercept Object.preventExtensions()
. The method must return a Boolean value, otherwise it will be automatically converted to a Boolean value.
This method has a limitation. Only when the target object is not scalable (ie Object.isExtensible(proxy)
is false
), proxy.preventExtensions
can return true
, otherwise an error will be reported.
const p = new Proxy({}, {
preventExtensions: function(target) {
console.log('called');
Object.preventExtensions(target);
return true;
}
});
console.log(Object.preventExtensions(p)); // "called"
// false
apply()
apply
method intercepts the following operations.
proxy(...args)
Function.prototype.apply()
andFunction.prototype.call()
Reflect.apply()
It accepts three parameters, which are the target object, the context object of the target object ( this
) and the parameter array of the target object.
const handler = {
apply (target, ctx, args) {
return Reflect.apply(...arguments);
}
};
example
const target = function () { };
const handler = {
apply: function (target, thisArg, argumentsList) {
console.log('called: ' + argumentsList.join(', '));
return argumentsList[0] + argumentsList[1] + argumentsList[2];
}
};
const p = new Proxy(target, handler);
p(1,2,3) // "called: 1, 2, 3" 6
construct()
Used to intercept the new
command, the following is the wording of the intercepted object:
const handler = {
construct (target, args, newTarget) {
return new target(...args);
}
};
Its method accepts three parameters.
target
: The target object.args
: The parameter array of the constructor.newTarget
: The constructornew
command when creating an instance object.
Note: The method must return an object, and the target object must be a function, otherwise an error will be reported.
const p = new Proxy(function() {}, {
construct: function(target, argumentsList) {
return 0;
}
});
new p() // 返回值不是对象,报错
const p = new Proxy({}, {
construct: function(target, argumentsList) {
return {};
}
});
new p() //目标对象不是函数,报错
Observer mode
Observer is a very commonly used mode. Its definition is that when an object's state changes, all objects that depend on it are notified and automatically updated.
We use Proxy to implement an example, when the state of the observation object changes, let the observation function automatically execute.
Observer function, wrap observation target, add observation function.
observable
wraps the observation target and returns a Proxy object.observe
Add the observation function to the queue.
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;
}
example
const user = observable({
name: 'jack',
age: 20
});
function userInfo() {
console.log(`${user.name}, ${user.age}`)
}
observe(userInfo);
user.name = '小明'; // 小明, 20
summary
To review the main points of this article, please leave a message for exchange.
- Built-in Reflect in JavaScript.
- Built-in Proxy in JavaScript.
- Proxy implements the observer mode.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。