所有文章搬運自我的個人主頁:sheilasun.me
最近打算試試看看jQuery的源碼,剛開個頭就卡住了。無論如何都理解不了jQuery源碼入口部分中的
return new jQuery.fn.init( selector, context )
看了好多帖子都沒看懂,覺得自己很蠢,心里很苦,吃宵夜都不香了。昨晚去游泳,游完8*100后靠在池壁上喘氣,有人從我旁邊出發,水花濺起的瞬間,我突然,想通了!這大概就是回光返照 (劃掉)福至心靈吧!
下面一點點地說下我對jQuery入口源碼的理解。
jQuery源碼最外層的結構如下:
(function(window,undefined){    ...})(window);任何庫的引入都得做到不污染全局變量,得有自己的命名空間。上面的自執行匿名函數就可以做到這點,把所有庫私有的變量和方法,都包到一個私有的空間內,允許外界訪問的屬性或方法可以掛載到window上。
例如下面這段代碼:
(function(){  var count=0;  var addOne=function(){    alert(count++);  };  window.outerAddOne=addOne; //掛到window上外界方可訪問})();outerAddOne();//alert "0"console.log(count);//errorconsole.log(addOne);//error內部定義的count變量以及addOne方法,外部環境下是無法訪問到的,但是在window上掛載一個方法outerAddOne,指向addOne,外界就可以訪問到了。
OK,了解了這個自執行匿名函數的作用,這里還有兩個問題。
看了上面的outerAddOne這個例子,就會發現,不傳入window也沒什么嘛,照樣可以把方法掛到window身上啊。
兩個原因:
我們用線上工具來壓縮混淆下面這段示例代碼:
function say(){  var name="naima";  window.description="hi "+name;}壓完混完后瘦了一點:
function say(){var a="naima";window.description="hi "+a}看到沒有,用a代替了name,但是window既不是聲明的局部變量也不是參數,是不會被壓縮混淆的,所以將window作為參數傳入可解決這個問題。
undefined并不是JS中的關鍵字,在IE8及以下中是可以對其重新賦值的。
var undefined="new value";alert(undefined);//alert “new value"在參數列表中給出undefined參數,但是不傳入值,那么這個參數值就是undefined值了。
先看jQuery源碼中如何對jQuery賦值的:
jQuery = function( selector, context ) {        // The jQuery object is actually just the init constructor 'enhanced'        return new jQuery.fn.init( selector, context, rootjQuery );    }    我就是被new jQuery.fn.init()這里弄暈了,先在這里暫停,回想一下平常我是如何使用jQuery的($即對應‘jQuery'):
$('body').CSS('background','red');$.parseJSON('{}');要實現這兩種調用,$('body')應該是一個實例對象,css是每個實例共享的方法,是原型上的方法。而$則是一個類,parseJSON則是類的靜態方法。
接下來,我們試著往這個結果上靠。
回想一下平常我都是怎么構建實例對象的,通常我會這樣寫一個PRince類:
function Prince(name){  this.name=name;  this.body="human";}Prince.prototype.change=function(){  this.body="frog";};然后我會這樣去獲取一個Prince實例對象:
var prince=new Prince("Harry");prince.change();如果我年紀大了忘記用new關鍵字了,程序就報錯了:
var a=Prince('harry');a.change();//error,"Cannot read property 'change' of undefined"除了調用方法會出錯之外,window還被掛載了兩個變量上去,何其無辜。
但是獲取jQuery對象(以下簡稱JQ對象)用new和不用new都可以,返回的是一樣樣的。
console.log($('*').length);//14console.log(new $('*').length);//14為了做到這點,我們很容易想到需要在構造函數內部返回對象。引用下我在另一篇博文javaScript中的普通函數與構造函數里寫的:
構造函數有return值怎么辦?
構造函數里沒有顯式調用return時,默認是返回this對象,也就是新創建的實例對象。
當構造函數里調用return時,分兩種情況:
1.return的是五種簡單數據類型:String,Number,Boolean,Null,Undefined。
這種情況下,忽視return值,依然返回this對象。
2.return的是Object
這種情況下,不再返回this對象,而是返回return語句的返回值。
所以我們應該在jQuery構造函數內部去返回一個對象,這樣就可以不用new的方式去創建JQ對象了,其實這時候,構造函數就相當于一個工廠函數了。
那么核心問題來了。
這個對象必須可以調用jQuery.prototype上的方法。
我們使用或自己寫jQuery插件的時候會經常遇到$.fn這個對象,很多插件都是通過擴展這個對象來實現的。
$.fn其實對應著jQuery.prototype,$和fn分別是jQuery和prototype的簡寫方式,只要我們把方法擴展到這個原型對象身上,通過$()獲取的JQ對象都是可以訪問到方法的。
例如:
$.fn.greeting=function(){alert('hi')};$('body').greeting();//alert 'hi'所以,工廠函數內部返回的對象一定要可以調用jQuery.prototype上的方法。
是時候看John Resig到底是怎么做的啦。
jQuery = function( selector, context ) {    return new jQuery.fn.init( selector, context, rootjQuery );},jQuery.fn = jQuery.prototype = { //fn即對應prototype    constructor: jQuery,    init: function( selector, context, rootjQuery ) {        ...        return this;    }    ...}jQuery.fn.init.prototype = jQuery.fn;在Chrome里調試時候添加JQ對象的watch,會看到類似如下的結果:
$('*'): n.fn.init[14]看到上面這段源碼,原因就很明顯了,其實我們所說的JQ對象根本就是init函數的實例對象,而init則是jQuery原型上的一個對象,它本身是沒有什么方法的,全靠從jQuery原型上拿。
"jQuery.fn.init.prototype = jQuery.fn"這句很重要,它將init的原型指向jQuery的原型,所以JQ對象才可以訪問‘css'、'show'、'hide'這些寫在jQuery.fn上的方法。
我們可能會有疑問,為何要從init這繞這么一大圈來訪問jQuery的原型,而不是直接返回一個jQuery實例直接通過這個實例來訪問自身原型?比如說代碼可以寫成這樣:
jQuery = function( selector, context ) {        return new jQuery();} 問題很明顯,這樣做只會大家一起死,死在循環里。
好,那我接受init的存在,但是我這樣寫難道不可以嗎?
jQuery = function( selector, context ) {        return jQuery.fn.init();//不同點在于去掉了new關鍵字}讓我們做點動作來證明加上new是有用的。
jQuery = function( selector, context ) {    return jQuery.fn.init();},jQuery.fn = jQuery.prototype = {    init: function() {            this.name='sheila';            return this;    },    anotherName:'sunwukong'};var jq=jQuery();console.log(jq.anotherName);//"sunwukong"console.log(jq.name);//"sheila"上面這段代碼是為了說明this的作用域問題,其不僅能訪問init函數內部,還能向上一層到fn對象。我聽人家說,做框架的,作用域要獨立才好呢。
給它加上new關鍵字:
...return new jQuery.fn.init();...console.log(jq.anotherName);//undefinedconsole.log(jq.name);//"sheila"這樣this的作用域就獨立出來了。
經博友評論提醒,加不加new還牽涉到一個更重要的問題:返回的對象究竟是誰。不加new的情況下,'jQuery.fn.init()'相當于調用方法,this指向的以及最后返回的都是同一個jQuery.fn對象,$('body')和$('p')就沒有區分了。顯然,這是不合理的。而加了new,就是每次用構造函數實例化了一個新對象,彼此都是不同的。
有任何不妥之處或錯誤歡迎各位指出,不勝感激~
經常看別人的博客,有些表述方式實在獨特而有趣,每每讀來都覺妙趣橫生,啞然失笑。不禁心生羨慕,技術過硬,知識面廣還寫得一手好文章,贊!
想起在學校時每次我們做presentation,上臺第一句,“大家好,我今天講的題目是……”,然后幻燈片一頁頁劃過去,“歷史背景”,“研究現狀”,“我使用的方法”……導師都聽得一臉崩潰,“nonono,不要,不要這樣,你們這樣講,不會有人有耐心聽下去的……我們要像說故事一樣娓娓道來,抓住聽眾的注意力,一點點引入……”于是以后我都盡量按照“說故事”這個思路去講,最后畢業答辯的時候,一個老師說,“為什么我覺得你像故宮導覽哈哈哈哈”……
果然還是沒有掌握表述的技巧啊。
新聞熱點
疑難解答