Detailed explanation of chain call in JavaScript

  • 2021-09-24 21:31:51
  • OfStack

Chain pattern

Chain mode is a form of chained invocation, which does not exactly belong to the commonly defined design pattern category, but it is a very useful code building technique.

Describe

Chain call is very common in JavaScript language, such as jQuery, Promise, etc., all of which use chain call. When we call the same object for many times its attributes or methods, we need to write the object for many times. Or () operation. Chain call is a coding way to simplify this process, so that the code is concise and easy to read.
Chain invocation is usually implemented in the following ways, but they are essentially similar, by returning objects for later invocation.

The scope chain of this, the implementation of jQuery, and usually the chain call are in this way. The return object itself is different from this in that it shows the return chain object. Closures return objects, which is similar to Currization.

var Person = function() {};
Person.prototype.setAge = function(age){
  this.age = age; 
  return this;
}
Person.prototype.setWeight = function(weight){
  this.weight = weight; 
  return this;
}
Person.prototype.get = function(){
  return `{age: ${this.age}, weight: ${this.weight}}`;
}

var person = new Person();
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}

var person = {
  age: null,
  weight: null,
  setAge: function(age){
    this.age = age; 
    return this;
  },
  setWeight: function(weight){
    this.weight = weight; 
    return this;
  },
  get: function(){
    return `{age: ${this.age}, weight: ${this.weight}}`;
  }
};
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}

function numsChain(num){
  var nums = num;
  function chain(num){
    nums = `${nums} -> ${num}`;
    return chain;
  }
  chain.get = () => nums;
  return chain;
}
var des = numsChain(1)(2)(3).get();
console.log(des); // 1 -> 2 -> 3

Optional chain operator

When it comes to chain calls, it is necessary to say that the optional chain operator of JavaScript under 1 belongs to the new feature operator of ES2020? .,? ? ,? ? =, optional chain operator? Allows the value of a property located deep in the chain of connected objects to be read without explicitly verifying that each reference in the chain is valid. ? The. operator functions similar to the. chain operator except that it does not cause an error if the reference is null nullish, i.e. null or undefined, and the short-circuit return value of the expression is undefined. When used with function call 1, undefined is returned if the given function does not exist. When trying to access an object property that may not exist, the optional chain operator will make the expression shorter and more concise. When exploring the contents of an object, the optional chain operator is also helpful if you are not sure which attributes must exist.

Grammar


obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)

Example


const obj = {a: {}};
console.log(obj.a); // {}
console.log(obj.a.b); // undefined
// console.log(obj.a.b.c); // Uncaught TypeError: Cannot read property 'c' of undefined
console.log(obj && obj.a); // {}
console.log(obj && obj.a && obj.a.b && obj.a.b.c); // undefined
console.log(obj?.a?.b?.c); // undefined

const test = void 0;
const prop = "a";
console.log(test); // undefined
console.log(test?.a); // undefined
console.log(test?.[prop]); // undefined
console.log(test?.[0]); // undefined
console.log(test?.()); // undefined

Chain Call in jQuery

jQuery is a high-end and luxurious framework, in which there are many wonderful methods and logic. Although it is very popular in the framework of MVVM mode similar to Vue and React, the design of jQuery is really great and worth learning. Here, taking the most basic instantiation of jQuery as an example, we explore how jQuery can be called chain through this.
Firstly, a basic class is defined, and methods are inherited through prototype chain.


function _jQuery(){}
_jQuery.prototype = {
  constructor: _jQuery,
  length: 2,
  size: function(){
    return this.length;
  }
}

var instance = new _jQuery();
console.log(instance.size()); // 2
// _jQuery.size() // Uncaught TypeError: _jQuery.size is not a function
// _jQuery().size() / /Uncaught TypeError: Cannot read property 'size' of undefined

By defining a class and instantiating it, Methods on prototypes can be shared between instances, It is obviously impossible to call directly through the _jQuery class. The first exception is thrown because there is no static method on the _ jQuery class, and the second exception is because _ jQuery does not return a value after being executed as a function. It can be seen from this that jQuery returns an object containing multiple methods when it is called by $(), but it is only accessible by itself, so we use another variable to access it.


function _jQuery(){
  return _fn;
}
var _fn = _jQuery.prototype = {
  constructor: _jQuery,
  length: 2,
  size: function(){
    return this.length;
  }
}
console.log(_jQuery().size()); // 2

In fact, in order to reduce the creation of variables, jQuery directly regards _fn as one attribute of _jQuery.


function _jQuery(){
  return _jQuery.fn;
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  length: 2,
  size: function(){
    return this.length;
  }
}
console.log(_jQuery().size()); // 2

At this point, we can really call the methods on the prototype in the way of _ jQuery (). However, in jQuery, the main goal of $() is to select elements as a selector, but now it returns a _ jQuery. fn object, which obviously fails to meet the requirements. In order to obtain the returned elements, it defines an init method on the prototype to obtain elements. Here, document. querySelector is directly used to save trouble. In fact, the selector construction of jQuery is very complicated.


function _jQuery(selector){
  return _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  init: function(selector){
    return document.querySelector(selector);
  },
  length: 3,
  size: function(){
    return this.length;
  }
}
console.log(_jQuery("body")); // <body>...</body>

It seems that the chain call this is omitted again, so we need to use the pointing of this here, because this always points to the object calling him when calling, so we can mount the selected element on the this object here.


function _jQuery(selector){
  return _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,
  init: function(selector){
    this[0] = document.querySelector(selector);
    this.length = 1;
    return this;
  },
  length: 3,
  size: function(){
    return this.length;
  }
}
var body = _jQuery("body");
console.log(body); // {0: body, length: 1, constructor: ƒ, init: ƒ, size: ƒ}
console.log(body.size()); // 1
console.log(_jQuery.fn); // {0: body, length: 1, constructor: ƒ, init: ƒ, size: ƒ}

However, there is another problem at this time. The elements selected by our selector are directly mounted on _ jQuery. fn. In this way, because the prototype is shared, the selector defined later will overwrite the selector defined earlier, which is obviously impossible, so we use new operator to create a new object.


var person = {
  age: null,
  weight: null,
  setAge: function(age){
    this.age = age; 
    return this;
  },
  setWeight: function(weight){
    this.weight = weight; 
    return this;
  },
  get: function(){
    return `{age: ${this.age}, weight: ${this.weight}}`;
  }
};
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}
0

So there is a problem again, When we instantiate _ jQuery. fn. init with new, the returned this points to an instance of _ jQuery. fn. init, We can't make chain calls. jQuery solves this problem with a very clever method, and directly points the prototype of _ jQuery. fn. init to _ jQuery. prototype. Although there will be the problem of circular reference, this one performance consumption is relatively nothing, so we have completed the implementation of jQuery selector and chain calls.


var person = {
  age: null,
  weight: null,
  setAge: function(age){
    this.age = age; 
    return this;
  },
  setWeight: function(weight){
    this.weight = weight; 
    return this;
  },
  get: function(){
    return `{age: ${this.age}, weight: ${this.weight}}`;
  }
};
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}
1

1 question per day

https://github.com/WindrunnerMax/EveryDay

The above is a detailed explanation of JavaScript chain call details, more information about JavaScript chain call please pay attention to other related articles on this site!


Related articles: