1. 程式人生 > 程式設計 >詳解JavaScript中的鏈式呼叫

詳解JavaScript中的鏈式呼叫

鏈模式

鏈模式是一種鏈式呼叫的方式,準確來說不屬於通常定義的設計模式範疇,但鏈式呼叫是一種非常有用的程式碼構建技巧。

描述

鏈式呼叫在JavaScript語言中很常見,如jQuery、Promise等,都是使用的鏈式呼叫,當我們在呼叫同一物件多次其屬性或方法的時候,我們需要多次書寫物件進行.或()操作,鏈式呼叫是一種簡化此過程的一種編碼方式,使程式碼簡潔、易讀。
鏈式呼叫通常有以下幾種實現方式,但是本質上相似,都是通過返回物件供之後進行呼叫。

  • this的作用域鏈,jQuery的實現方式,通常鏈式呼叫都是採用這種方式。
  • 返回物件本身,同this的區別就是顯示返回鏈式物件。
  • 閉包返回物件的方式實現,這種方式與柯里化有相似之處。
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

可選鏈操作符

說到鏈式呼叫,就有必要說一下JavaScript的可選鏈操作符,屬於ES2020新特性運算子?.、??、??=,可選鏈操作符?.允許讀取位於連線物件鏈深處的屬性的值,而不必明確驗證鏈中的每個引用是否有效。?.操作符的功能類似於.鏈式操作符,不同之處在於在引用為空nullish即null或者undefined的情況下不會引起錯誤,該表示式短路返回值是undefined。與函式呼叫一起使用時,如果給定的函式不存在,則返回undefined。當嘗試訪問可能不存在的物件屬性時,可選鏈操作符將會使表示式更短更簡明。在探索一個物件的內容時,如果不能確定哪些屬性必定存在,可選鏈操作符也是很有幫助的。

語法

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

示例

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

jQuery中的鏈式呼叫

jQuery是一個高階而不失奢華的框架,其中有許多非常精彩的方法和邏輯,雖然現在非常流行於類似於Vue、React的MVVM模式的框架,但是jQuery的設計實在是棒,非常值得學習,在這裡以最基礎的例項化jQuery為例探查一下jQuery如何通過this實現的鏈式呼叫。
首先定義一個最基本的類,通過原型鏈去繼承方法。

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

通過定義一個類並且實現例項化之後,在例項之間可以共享原型上的方法,而直接通過_jQuery類直接去呼叫顯然是不行的,丟擲的第一種異常是因為在_jQuery類上不存在靜態方法,第二種異常是因為_jQuery作為函式執行後未返回值,通過這裡可以看出jQuery在通過$()方式呼叫的時候是返回了一個包含多個方法的物件的,而只是通過自己是訪問不到的,我們就藉助另一個變數去訪問。

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

實際上jQuery為了減少變數的建立,直接將_fn看做了_jQuery的一個屬性。

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

到這裡確實能夠實現_jQuery()方式呼叫原型上的方法,但是在jQuery中$()的主要目標還是作為選擇器用來選擇元素,而現在返回的是一個_jQuery.fn物件,顯然是達不到要求的,為了能夠取得返回的元素,那就在原型上定義一個init方法去獲取元素,這裡為了省事直接使用了document.querySelector,實際上jQuery的選擇器構建是很複雜的。

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>

是似乎這樣又把鏈式呼叫的this給漏掉了,這裡就需要利用this的指向了,因為在呼叫時this總是指向呼叫他的物件,所以我們在這裡將選擇的元素掛載到this物件上即可。

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;
  },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,size: ƒ}

但是此時又出現了一個問題,我們的選擇器選擇的元素是直接掛載到了_jQuery.fn上,這樣的話由於原型是共享的,在之後的定義的選擇器就會將前邊定義的選擇器覆蓋掉,這樣顯然是不行的,於是我們使用new操作符新建一個物件。

function _jQuery(selector){
  return new _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,size: function(){
    return this.length;
  }
}
var body = _jQuery("body");
console.log(body); // init {0: body,length: 1}
// console.log(body.size()); // Uncaught TypeError: body.size is not a function

這樣又出現了問題,當我們使用new例項化_jQuery.fn.init時返回的this指向的是_jQuery.fn.init的例項,我們就不能進行鏈式呼叫了,jQuery用了一個非常巧妙的方法解決了這個問題,直接將_jQuery.fn.init的原型指向_jQuery.prototype,雖然會有迴圈引用的問題,但是相對來說這一點效能消耗並不算什麼,由此我們完成了jQuery選擇器以及鏈式呼叫的實現。

function _jQuery(selector){
  return new _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
  constructor: _jQuery,size: function(){
    return this.length;
  }
}
_jQuery.fn.init.prototype = _jQuery.fn;
var body = _jQuery("body");
console.log(body); // init {0: body,length: 1}
console.log(body.size()); // 1
console.log(_jQuery.fn.init.prototype.init.prototype.init.prototype === _jQuery.fn); // true

每日一題

https://github.com/WindrunnerMax/EveryDay

以上就是詳解JavaScript中的鏈式呼叫的詳細內容,更多關於JavaScript 鏈式呼叫的資料請關注我們其它相關文章!