鏈模式
鏈模式是一種鏈式調用的方式,鏈模式不屬於一般定義的23
種設計模式的范疇,而通常將其看作廣義上的技巧型設計模式。
描述
鏈式調用在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,
length: 2,
size: function(){
return this.length;
}
}
console.log(_jQuery().size()); // 2
實際上jQuery
為了減少變量的創建,直接將_fn
看做了_jQuery
的一個屬性。
function _jQuery(){
return _jQuery.fn;
}
_jQuery.fn = _jQuery.prototype = {
constructor: _jQuery,
length: 2,
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;
},
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: ƒ}
但是此時又出現了一個問題,我們的選擇器選擇的元素是直接掛載到了_jQuery.fn
上,這樣的話由於原型是共享的,在之后的定義的選擇器就會將前邊定義的選擇器覆蓋掉,這樣顯然是不行的,於是我們使用new
操作符新建一個對象。
function _jQuery(selector){
return new _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); // 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,
init: function(selector){
this[0] = document.querySelector(selector);
this.length = 1;
return this;
},
length: 3,
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
參考
https://zhuanlan.zhihu.com/p/110512501
https://juejin.cn/post/6844904030221631495
https://segmentfault.com/a/1190000011863232
https://github.com/songjinzhong/JQuerySource
https://leohxj.gitbooks.io/front-end-database/content/jQuery/jQuery-source-code/index.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/%E5%8F%AF%E9%80%89%E9%93%BE