寫在最前
這不是一篇分析原始碼的文章——因為作者也沒有怎麼看原始碼。本文主要分析jQuery中到底是如何進行構造原型鏈的。思路是通過逆推來丟擲問題推測答案再用正推的方式來分析梳理。歡迎關注作者部落格,不定期更新中——
jQuery是什麼
首先你知道jQuery有兩種使用方法吧?
一種是jQuery('#xxx');一種是new jQuery('#xxx');
這兩種方式都會返回一個例項。其原型鏈應該有一大堆方法。比如:jQuery('#xxx').css;jQuery('#xxx').attr;jQuery('#xxx').html...等等。
並且我們應該認識到jQuery這個函式一方面返回了一個例項,另一方面其本身也是建構函式(因為 new jQuery),那麼其原型鏈也應該指向了那一大堆方法。我們一步步列印一下來驗證下猜測:
console.log(jQuery) // 來看下jQuery函式體
function ( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
} //小技巧,可以引入沒有壓縮過的jQuery進行學習,這樣備註,變數名會抱持原樣。複製程式碼
好的果然沒猜錯,我們看到了一個建構函式為jQuery.fn.init
。通過new這個建構函式就可以採用第一種jQuery()的形式來生成例項。接下來驗證下jQuery.fn.init
的prototype屬性上是不是有我們猜測的一大堆方法。
console.log(Object.keys(jQuery.fn.init.prototype))
// ["jquery","constructor","init","show","hide","toggle","on","one", "off","detach","remove","text","append", ...]複製程式碼
從結果中也可以知道我們的推測是正確的。在jQuery.fn.init
的prototype中有著封裝的方法可供例項呼叫。
new jQuery('#xxx')
驗證了無new構造例項的形式之後再來看下對於jQuery同時應該是個建構函式的猜測。
console.log(Object.keys(jQuery.prototype))
//["jquery","constructor","init","show","hide","toggle","on","one", "off","detach","remove","text","append", ...]
console.log(jQuery.prototype === jQuery.fn.init.prototype) //true複製程式碼
可以看出jQuery也確實是一個建構函式其prototype和jQuery.fn.init的一樣都指向了那一大堆方法。
init方法
讓我們再看下這段程式碼:
function ( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
}複製程式碼
這裡面返回的建構函式jQuery.fn.init
我們可以看成是呼叫了jQuery.fn的init方法。同時細心的同學們應該可以觀察到,在jQuery.fn.init.prototype
中也有個方法叫init!。那麼是不是。。讓我們列印一下我們的猜測:
console.log(jQuery.fn.init.prototype.init === jQuery.fn.init) //true複製程式碼
發現了麼同學們!既然jQuery.fn可以呼叫jQuery.fn.init其原型鏈上的方法,那麼一定有:
jQuery.fn.init.prototype === jQuery.fn // true複製程式碼
小結
好的現在大家可能有種似懂非懂的感覺?來看下面這張圖來總結下我們的發現。
通過前文加上我們上圖的展示,原型鏈的關係已經很明瞭了。在原型鏈上繫結了很多很多方法確定無疑。與此同時有三個東西指向了該原型鏈即:
jQuery.fn === jQuery.fn.init.prototype //true
jQuery.fn.init.prototype === jQuery.prototype //true複製程式碼
在完成這三個的指向之後就可以滿足我們起初的需求:
- 呼叫jQuery()可以返回一個例項
- jQuery自己也是建構函式可以被顯式new來構建例項
- 例項的方法繫結在了原型鏈上
當然了jQuery裡面還有方法是繫結在jQuery本身的,繫結在原型鏈上的方法通過jQuery('#xxx').xxx
呼叫,這個是相對某個元素的方法,繫結在jQuery本身的方法通過$.xxx
呼叫,這個是某種特定的靜態方法。我們現在只是討論基礎的jQuery在最外層構建時這些prototype屬性都是怎麼關聯的。想深入瞭解的歡迎去讀原始碼——
正向梳理一遍
再回過頭來看上文提到的三個需求:
- 呼叫jQuery()可以返回一個例項
- jQuery自己是建構函式可以被顯式new來構建例項
- 例項的方法繫結在了原型鏈上
如果讓你來寫一個你怎麼寫?ok,我們一步一步來
呼叫jQuery()可以返回一個例項
//v1.0
var j = function(selector){
return new init(selector);
}
var init = function() {...}複製程式碼
返回的這個例項可以呼叫原型鏈方法
//v2.0
//即fn.init的原型應該是j.prototype
var fn = {}
var xxx = function() {}
fn.init = function(selector) {console.log(selector)}
var j = function(selector){
return new fn.init(selector);
}
xxx.prototype = {
setColor: function(color){console.log(color)}
...
}
fn.init.prototype = xxx.prototype
var a = new j(1) //1
a.setColor('red') // red複製程式碼
init方法也要從原型鏈上呼叫
//v3.0
var xxx = function() {}
var j = function(selector){
return new j.fn.init(selector); //借用j.fn來找到原型鏈方法,不然找不到
}
j.fn = xxx.prototype = { //j本身是建構函式
init: function(selector) {
console.log(selector)
},
setColor: function(color) {
console.log('setColor:' + color)
}
}
j.fn.init.prototype = j.fn
var a = new j(1)
a.setColor('red')複製程式碼
jQuery自己是建構函式可以被顯式new來構建例項
//v4.0
//將xxx替換為j,那麼j當做建構函式後其原型鏈也指向了那一堆方法
var j = function(selector){
return new j.fn.init(selector); //借用j.fn來找到原型鏈方法,不然找不到
}
j.fn = j.prototype = { //j本身是建構函式
init: function(selector) {
console.log(selector)
},
setColor: function(color) {
console.log('setColor:' + color)
}
}
j.fn.init.prototype = j.fn
var a = new j(1)
a.setColor('red')複製程式碼
至此我們便寫好了一個jQuery初級版原型鏈的一個構建。裡面很多操作更多的是為了讓暴露的變數儘可能的少,所以在原型鏈構件上有一種迴圈賦值的趕腳哈哈哈。有興趣的同學可以繼續研究。
最後
慣例po作者的部落格,不定時更新中——
有問題歡迎在issues下交流,捂臉求star=。=