This is the 10th day of my participation in Gwen Challenge

A brief introduction to

Delegates is a kit developed by TJ for implementing simple delegates, used in Koa. Koa makes invoking methods easier by using delegates to the context of context.request and context.Response, For example, the context.request.query method can be written as context.query.

Delegates: Simple use

If the dependency package is installed in the project by NPM I DELEGATES, delegates are able to use the following simple example:

var delegate = require('delegates');

var obj = {};
obj.request = {
    foo: function(){
        console.log('do something ... ');
    },
    get name() {
        return this._name
    },
    set name(val) {
        this._name = val
    }
};
// Delegate obj. Request attributes to obj to make the call easier
delegate(obj, 'request').method('foo').getter('name').setter('name');
obj.foo();  // do something ...
obj.name = 'LvLin'; 
console.log(obj.name); // 'LvLin'
Copy the code

Source code analysis

Delegates the delegates source code consists of 157 lines implementing a Delegator class with constructor, static method auto and prototype method, Access, getter, setter, Fluent.

module.exports = Delegator;
// constructor
function Delegator(proto, target) {... }// Static method
Delegator.auto = function(proto, targetProto, targetProp) {... }// Prototype method
Delegator.prototype.method = function(name) {... } Delegator.prototype.access =function(name) {... } Delegator.prototype.getter =function(name) {... } Delegator.prototype.setter =function(name) {... } Delegator.prototype.fluent =function(name) {... }Copy the code

The constructor

The constructor first determines whether the current function is called by new, and if not, creates a Delegator instance to return, so we call via delegate(Proto, target) as new delegate(Proto, target).

The attributes are then defined as follows:

function Delegator(proto, target) {
   // Decide whether to call with new, if not create instance with new
  if(! (this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}
Copy the code

Prototype method

The method () method is used to bind a method on target to a proTO closure and return a Delegator instance object to make a chained call.

Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name); // Store it in the methods array

 // Convert a call to the proto method into a call to the related method on this[target] as a closure
 // apply changes this to this[target]
  proto[name] = function(){
    return this[target][name].apply(this[target], arguments);
  };
  // Return the Delegator instance object to implement the chain call
  return this;
};
Copy the code

Getter and setter methods are used to bind property (getter or setter) proto[target][name] to proto[name]. Access contains both getter and setter. The source code is as follows:

Delegator.prototype.access = function(name){
  
  return this.getter(name).setter(name);
};

Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name); // Store the property name in an array of the corresponding type

  // Set the getter for proto with __defineGetter__,
  Proto [target][name] = proto[target]
  proto.__defineGetter__(name, function(){
    return this[target][name];
  });
  // Return the delegator instance to implement the chain call
  return this;
};

Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name); // Store the property name in an array of the corresponding type

  // Set the setter for proto with __defineSetter__,
  Proto [target][name] = proto[target]
  proto.__defineSetter__(name, function(val){
    return this[target][name] = val;
  });
// Return the delegator instance to implement the chain call
  return this;
};
Copy the code

Delegates configure the getter and setter for an object through its __defineGetter__ and __defineSetter__ methods, but those methods have been removed from the Web standard and it is recommended not to use this feature. Currently recommended is Object. The prototype. DefinePrototype. About setter and getter and Object. The prototype. DefinePrototype knowledge, can I know of this article.

The Fluent method is used to delegate the common attribute proto[target][name] to proto[name] so that proto[target][name] can be accessed and modified by proto[name]. The source code is as follows:

Delegator.prototype.fluent = function (name) {
  var proto = this.proto;
  var target = this.target;
  this.fluents.push(name); // Store the property name in an array of the corresponding type

  proto[name] = function(val){
    // Check whether val is null or undefined to determine whether the value is assigned
    if ('undefined'! =typeof val) {
      // Assign, modify proto[target][name], return proto
      this[target][name] = val;
      return this;
    } else {
      // Obtain the value of proto[target][name]
      return this[target][name]; }};return this;
};
Copy the code

As you can see, if you need to get or modify a common attribute proto[target][name], you can only get or call it as a function, as follows:

var obj = {};
obj.request = {
    name: ' ' 
};

delegate(obj, 'request').fluent('name');
obj.name('LvLin');
console.log(obj.name()) // LvLin
Copy the code

Static method auto

The method accepts an object proto, an internal object targetProto, and the attribute name of the internal object targetProp. It automatically delegates the attribute of targetProp object in PROto to ProTO, and targetProp provides the basis for the judgment of the delegate. The source code analysis is as follows:

Delegator.auto = function(proto, targetProto, targetProp){
  // Build a Delegator instance
  var delegator = Delegator(proto, targetProp);
  // Get all attributes on targetProto
  var properties = Object.getOwnPropertyNames(targetProto);
  // Iterate over the properties to determine how to delegate to proto
  for (var i = 0; i < properties.length; i++) {
    var property = properties[i];
    // Gets the property descriptor for this property
    var descriptor = Object.getOwnPropertyDescriptor(targetProto, property);
    // Store the descriptor case, invoke the relevant API implementation delegate
    if (descriptor.get) {
      delegator.getter(property);
    }
    if (descriptor.set) {
      delegator.setter(property);
    }
	// Data descriptors
    if (descriptor.hasOwnProperty('value')) {
      var value = descriptor.value;
      // If it is a function, delegate to method
      if (value instanceof Function) {
        delegator.method(property);
      } else {
       // Otherwise, do the getter delegate
        delegator.getter(property);
      }
      // If it can be overridden, do setter delegate
      // There is something wrong with it. If it is a Function, you should not do this
      // But if defined with Object.defineProperty, writable defaults to false
      if(descriptor.writable) { delegator.setter(property); }}}};Copy the code

Object property descriptors, I have a detailed explanation in this article, welcome to read ~

conclusion

The delegate divides the object properties into four categories: Fluent, getter, setter and Method. The corresponding interfaces are provided to implement the delegate operation of the object properties, and simple access and auto methods are also provided.

The apis in the Delegate all end up returning the Delegator instance object that calls the API, thereby implementing chain calls from multiple apis.

data

… node-delegates source code, by TJ Holowaychuk

Object. The prototype. __defineGetter__ (), by MDN