鏈模式是一種鏈式調用的方式,準確來講不屬於一般定義的設計模式範疇,但鏈式調用是一種很是有用的代碼構建技巧。javascript
鏈式調用在JavaScript
語言中很常見,如jQuery
、Promise
等,都是使用的鏈式調用,當咱們在調用同一對象屢次其屬性或方法的時候,咱們須要屢次書寫對象進行.
或()
操做,鏈式調用是一種簡化此過程的一種編碼方式,使代碼簡潔、易讀。
鏈式調用一般有如下幾種實現方式,可是本質上類似,都是經過返回對象供以後進行調用。html
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
。當嘗試訪問可能不存在的對象屬性時,可選鏈操做符將會使表達式更短更簡明。在探索一個對象的內容時,若是不能肯定哪些屬性一定存在,可選鏈操做符也是頗有幫助的。java
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
是一個高端而不失奢華的框架,其中有許多很是精彩的方法和邏輯,雖然如今很是流行於相似於Vue
、React
的MVVM
模式的框架,可是jQuery
的設計實在是棒,很是值得學習,在這裏以最基礎的實例化jQuery
爲例探查一下jQuery
如何經過this
實現的鏈式調用。
首先定義一個最基本的類,經過原型鏈去繼承方法。jquery
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
在經過$()
方式調用的時候是返回了一個包含多個方法的對象的,而只是經過本身是訪問不到的,咱們就藉助另外一個變量去訪問。git
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
的一個屬性。github
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
的選擇器構建是很複雜的。segmentfault
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