6

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() and Function.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 constructor new 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.

编程码农
455 声望1.4k 粉丝

多年编程老菜鸟👨‍💻🦍