链模式是一种链式调用的方式,准确来讲不属于一般定义的设计模式范畴,但链式调用是一种很是有用的代码构建技巧。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