無限呼叫之鏈模式分析
鏈模式相信大家很常見,尤其在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 } }) 複製程式碼