無限呼叫之鏈模式分析

RobinsonZhang發表於2019-01-22

前言

鏈模式相信大家很常見,尤其在jq中,我們非常習慣的可以使用jq的鏈式模式中連續的進行方法的呼叫,雖然我們知道其在方法執行後會返回this的當前物件,來實現這個模式的設計,我們還是進行更加詳細的研究學習吧。

基於原型的繼承

首先我們知道的是鏈模式是基於原型,比如我們在假設我們的方法為A。我們現在的方法和屬性都是定義在原型鏈上,a本身是方法,我們通過例項化方法可以訪問到原型鏈上的方法。

function A (){
}A.prototype = {
length:2, size:function(){
return this.length
}
}let a = new A()console.log(a.size()) //2// 沒有size 是因為方法定義在原型鏈上console.log(A.size())// 沒有size執行是因為a方法的執行沒有任何返回值console.log(A().size())複製程式碼

但是這樣是有問題的,我們知道jq的方式是直接用返回物件並沒有例項化的方法,那我們可以進行藉助另一個物件來實現。

藉助助手

我們可以藉助另外的物件進行,上面講到A().size()不行是因為a方法沒有任何任何值,那麼我們可以進行返回。如下的方式改造就可以直接a方法呼叫函式了。

function A (){ 
return B
}let B = A.prototype = {
length:2, size:function(){
return this.length
}
}console.log(A().size()) //2// 為了減少變數的建立,我們直接用A的屬性來承接A.fn = A.prototype = {
length:2, size:function(){
return this.length
}
}複製程式碼

獲取元素

我們知道jq最終是為了獲取元素的集合,所以目前的方式肯定是不合適的。所以我們需要在原型物件上定義一個init方法來獲取物件。那麼我們需要在A方法執行的時候直接返回我們的初始化方法。

function A (seletor){ 
return A.fn.init(seletor)
}A.fn = A.prototype = {
init:function(seletor){
return document.getElementById(seletor)
}, length:2, size:function(){
return this.length
}
}console.log(A("demo")) // dom 複製程式碼

返回我們需要的方法

雖然之前的設計可以得到元素了,但這樣的元素不具有我們想要的方法,那麼如何讓返回的元素具有我們想要的方法呢,在方法體重我們可以看到A.fn是具有我們的方法的。

function A (seletor){ 
return A.fn.init(seletor)
}A.fn = A.prototype = {
init:function(seletor){
this[0] = document.getElementById(seletor) this.length = 1 return this
}, length:2, size:function(){
return this.length
}
}console.log(A("demo").size()) // 1 得到校驗後的長度複製程式碼

此時發現另外的問題了,就是我們進行另外一個元素的使用時,元素會互相覆蓋,原因是因為我們每次都是返回利用的同一個物件。

覆蓋獲取

解決覆蓋獲取的方式也很簡單,恢復例項化即可。但這樣也會導致方法無法使用。報錯如下:A(...).size is not a function

function A (seletor){ 
return new A.fn.init(seletor)
}複製程式碼

方法丟失

原因是因為前者返回的是當前物件,也就是A.fn和A.prototype,但如果是new的進行的是屬性的複製,與前面不同。

經過測試,init方法中第一種方式返回的確實是a.fn,a.prototype,而如果是返回new a.fn.init,那麼返回的是a.fn.a.init

jq的解決方案

直接將a.fn的原型指向存在的物件即可。

 A.fn.init.prototype = A.fn複製程式碼

這樣就可以滿足我們的基本需求了。

豐富的元素獲取

上面的程式碼可以實現獲取id元素的標籤,但實際上我們有各種可能,這種時候只要判斷選擇器的內容,然後針對性的獲取元素就好。

init:function(selector,context){ 
this.length = 0 context = context || document if(~selector.indexOf("#")>
-1){
this[0] = document.getElementById(selector.slice(1)) this.length = 1
} ...
}複製程式碼

陣列與物件

雖然我們實現了基本的方法,但發現仍然存在一個使用上的特性,就是jq的元素支援陣列的很多操作,為了讓我們的返回物件也具有類似的功能,我們需要在原型鏈上追加push\sort\split等類似陣列的方法來實現。

A.fn = A.prototype = { 
// 陣列方法新增 增強陣列特性 push:[].push, sort:[].sort, splice:[].splice
}複製程式碼

方法擴充extend

A.extend = A.fn.extend = function(){ 
var i = 1,len = arguments.length, target = arguments[0] if(i === len){
target = this ;
i--
} for(;
i<
len;
i++){
for(j in arguments[i]){
target[j] = arguments[i][j]
}
} return target ;

}複製程式碼

新增方法,返回this

所以如果你要新增方法,你直接用就可以。

A.fn.extend = ({ 
html:function(){
var arg = arguments,len = arguments.length;
if(len === 0){
return this[0] &
&
this[0].innerHtml;

} else {
for(var i = this.length -1 ;
i >
=0 ;
i-- ){
this[i].innerHtml = arg[0]
}
} // 返回this 支援鏈式呼叫 return this
}
})複製程式碼

來源:https://juejin.im/post/5c472dd36fb9a049f06a9806

相關文章