全部文章搬運自個人我的主頁:sheilasun.mecss
最近打算試試看看jQuery的源碼,剛開個頭就卡住了。不管如何都理解不了jQuery源碼入口部分中的html
return new jQuery.fn.init( selector, context )jquery
看了好多帖子都沒看懂,以爲本身很蠢,內心很苦,吃宵夜都不香了。昨晚去游泳,游完8*100後靠在池壁上喘氣,有人從我旁邊出發,水花濺起的瞬間,我忽然,想通了!這大概就是迴光返照 (劃掉)福至心靈吧!
下面一點點地說下我對jQuery入口源碼的理解。chrome
jQuery源碼最外層的結構以下:框架
(function(window,undefined){ ... })(window);
任何庫的引入都得作到不污染全局變量,得有本身的命名空間。上面的自執行匿名函數就能夠作到這點,把全部庫私有的變量和方法,都包到一個私有的空間內,容許外界訪問的屬性或方法能夠掛載到window上。ide
例以下面這段代碼:函數
(function(){ var count=0; var addOne=function(){ alert(count++); }; window.outerAddOne=addOne; //掛到window上外界方可訪問 })(); outerAddOne();//alert "0" console.log(count);//error console.log(addOne);//error
內部定義的count變量以及addOne方法,外部環境下是沒法訪問到的,可是在window上掛載一個方法outerAddOne,指向addOne,外界就能夠訪問到了。工具
OK,瞭解了這個自執行匿名函數的做用,這裏還有兩個問題。this
看了上面的outerAddOne這個例子,就會發現,不傳入window也沒什麼嘛,照樣能夠把方法掛到window身上啊。
兩個緣由:prototype
咱們用線上工具來壓縮混淆下面這段示例代碼:
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);//14 console.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);//undefined console.log(jq.name);//"sheila"
這樣this的做用域就獨立出來了。
經博友評論提醒,加不加new還牽涉到一個更重要的問題:返回的對象到底是誰。不加new的狀況下,'jQuery.fn.init()'至關於調用方法,this指向的以及最後返回的都是同一個jQuery.fn對象,$('body')和$('p')就沒有區分了。顯然,這是不合理的。而加了new,就是每次用構造函數實例化了一個新對象,彼此都是不一樣的。
有任何不妥之處或錯誤歡迎各位指出,不勝感激~
常常看別人的博客,有些表述方式實在獨特而有趣,往往讀來都覺妙不可言,啞然失笑。不由心生羨慕,技術過硬,知識面廣還寫得一手好文章,贊! 想起在學校時每次咱們作presentation,上臺第一句,「你們好,我今天講的題目是……」,而後幻燈片一頁頁划過去,「歷史背景」,「研究現狀」,「我使用的方法」……導師都聽得一臉崩潰,「nonono,不要,不要這樣,大家這樣講,不會有人有耐心聽下去的……咱們要像說故事同樣娓娓道來,抓住聽衆的注意力,一點點引入……」因而之後我都儘可能按照「說故事」這個思路去講,最後畢業答辯的時候,一個老師說,「爲何我以爲你像故宮導覽哈哈哈哈」…… 果真仍是沒有掌握表述的技巧啊。