链模式
链模式是一种链式调用的方式,链模式不属于一般定义的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