天天看點

[轉載]jQuery誕生記-原理與機制

by zhangxinxu from http://www.zhangxinxu.com

本文位址:http://www.zhangxinxu.com/wordpress/?p=3520

一、看似偶然的東西實際是必然會發生的

我大學時候在圖書館翻過一本很破舊的書,講生實體論的,主要内容就是探讨生命的産生是偶然還是必然。裡面很多亞裡士多德都看不懂的公式計算什麼的,還有模拟原始地球環境出現了有機物的實驗什麼的 

[轉載]jQuery誕生記-原理與機制

。總之,書論述的觀點是:“在當時的地球環境下,生命的産生是必然的!” 無數次機會的偶然條件、無數次化合物的相遇反應等必定會産生有機物,再有N多偶然,有機物必然形成了有機體……

這種理論類似于,你是個過馬路非常小心的人,且你萬壽無疆,除了怕被汽車撞。給你100萬年的壽命,你最後必然還是被車撞死。

如果以這種理論來看jQuery的出現,結論也應該是必然的!

二、需求、動力、發展、事物産生與jQuery的誕生

[轉載]jQuery誕生記-原理與機制

一個成熟的東西顯然不是一口氣就出來的,所謂“一鏟子挖不了一口井”,我想jQuery的原作者再天才,也是循序漸進過來的,如何個循序漸進法,我想,很有可能就是需求驅動而産生的,好比上圖刀削面機器人,據說現在已經第八代了!

1. gelElementById太長了

頁面上有個按鈕,還有個圖檔,我想點選按鈕圖檔隐藏,如下HTML:

<button id="button">點選我</button>
<img id="image" src="xxx.jpg">
      

于是,我的腳本可能就這樣:

var button = document.getElementById("button")
    , image = document.getElementById("image")

button.onclick = function() {
    image.style.display = "none";
};
      

有何問題?人幾乎都是天生的“懶惰者”,

document.getElementById

名稱長且重複出現,好像到了公司發現卡沒帶又回家重新拿卡的感覺,我希望越簡單越好。恩,

[轉載]jQuery誕生記-原理與機制

 我很喜歡錢,

$

這個符号我很喜歡,我打算改造一番,簡化我的工作:

var $ = function(id) {
    return document.getElementById(id);
};

$("button").onclick = function() {
    $("image").style.display = "none";
};
      

這裡的

$()

就是最簡單的包裝器,隻是傳回的是原生的DOM對象。

2. 我需要一個簡潔的暗号,就像“芝麻開門”

後來頁面複雜了,點選一個按鈕,有2張圖檔要隐藏。

$("button").onclick = function() {
    $("image1").style.display = "none";
    $("image2").style.display = "none";
};      

好像又看見長長的重複的東西,

xxx.style.display = "none"

, 為什麼每次開門都要從包裡找到鑰匙、對準插口插進去、還要左扭扭右扭扭呢?一次還好,天天經常老是這樣怎受得了。設想,要是有個芝麻開門的暗号就好了,“open開”,聲音識别,門自動開了,多省心。

這裡每次隐藏都要

xxx.style.display = "none"

, 比每天拿鑰匙開門還要煩,我希望有一個快捷的方式,例如,“hide隐”,語句識别,元素自動隐藏,多省心。

就是要變成下面的效果:

$("button").onclick = function() {
    $("image1").hide();
    $("image2").hide();
};      

3. 如何識别“芝麻開門”的暗号

$("image1")

本質是個DOM元素,

$("image1").hide()

也就是在DOM元素上擴充一個

hide

方法,調用即隐藏。

哦,擴充,立馬想到了JS中的

prototype

原型。//zxx: 老闆,現在滿大街什麼菜最便宜。老闆:原型啊,都泛濫了!

HTMLElement.prototype.hide = function() {
    this.style.display = "none";
};
      

上面代碼的demo位址應該不會被人看到吧……

雖然在身體上鑽了個窟窿插進入了一個方法,畢竟浏覽器有有效果啊,切膚之痛就不算什麼了。但是,我們是在泱泱天朝,很多IE6~IE8老頑固,這些老東西不認識

HTMLElement

,對于

HTMLElement

自殘擴充之法根本了解不了,而這些老家夥掌管了半壁江山。唉,面對現實,元素直接擴充是行不通了。

是以,由于相容性,我們需要想其他擴充方法。

4. 條條大路通羅馬,此處不留爺,自有留爺處

雖IE6~IE8不識

HTMLElement

原型擴充,但是,

Function

的原型擴充其認識啊。管不管用,暫時不得而知,先随便搞個簡單的試試呗~

var F = function() {};
F.prototype.hide = function() {
    this?.style.display = "none";
};

new F().hide();    // 這個實作隐藏?      

本文至少還有一半的内容,但是,全文的最難點就在這裡的,對

new F()

的認識和了解。

上面的代碼,new F()您可以看做是

this?.style

這裡的

this

. 您可能會跳起來搶答道:“那

new F()

return

值 = 

DOM元素

不就完事OK啦!—— 

this.style.hide

 = 

new F().style.hide

 = 

DOM.style.hide

”!

[轉載]jQuery誕生記-原理與機制
隻要

new

表達式之後的

constructor

傳回(

return

)一個引用對象(數組,對象,函數等),都将覆寫new建立的匿名對象,如果傳回(

return

)一個原始類型(無

return

時其實為

return

原始類型

undefined

),那麼就傳回

new

建立的匿名對象。

上面的引用來自這裡。什麼意思呢?說白了就是,

new F()

如果沒有傳回值(

Undefined

類型),或傳回值是5種基本型(

Undefined

類型、

Null

類型、

Boolean

類型、

Number

類型、

String

類型)之一,則

new F()

我們可以看成是原型擴充方法中的

this

; 如果傳回是是數組啊、對象啊什麼的,則傳回值就是這些對象本身,此時

new F()

 ≠ 

this

舉例說明:

var F = function(id) {
    return document.getElementById(id);
};

new F("image1") == document.getElementById("image1");    // true 說明看上去傳回DOM對象,實際确實就是DOM對象      
var F = function(id) {
    return id;
};

new F("image1") == "image1";    // false 說明看上去傳回字元串值,實際并不是字元串      

回到上面天真的想法。要想使用

prototype.hide

方法中的

this

, 偶們就不能讓

F

函數有亂七八糟的傳回值。

是以,

new F()

直接傳回DOM是不可取的,但我們可以借助

this

間接調用。比方說:

var F = function(id) {
    this.element = document.getElementById(id);
};
F.prototype.hide = function() {
    this.element.style.display = "none";
};

new F("image").hide();    // 看你還不隐藏      

上面代碼的demo位址應該不會被人看到吧……

5. 我不喜歡太暴露

F()

中的

this.element

實際上将

element

這個屬性直接暴露在了

new F("image")

上!

new F("image").hasOwnProperty("element");    // true      
[轉載]jQuery誕生記-原理與機制

太暴露了,我不喜歡~~

[轉載]jQuery誕生記-原理與機制

如何隐藏?代碼如下:

var F = function(id) {
    return this.getElementById(id);
};
F.prototype.getElementById = function(id) {
    this.element = document.getElementById(id);
    return this;
};
F.prototype.hide = function() {
    this.element.style.display = "none";
};

new F("image").hide();    // 看你還不隐藏      

元素擷取方法放在

prototype

上,通過

F()

執行。你可能會奇怪了,你剛明明說“

new F()

直接傳回DOM是不可取的”,怎麼現在又有

return

呢?大家務必擦亮眼睛,

F.prototype.getElementById

的傳回值是

this

,也就是

new F()

的傳回值是

this

. 形象點就是

new F("image")

出了一拳,又反彈到自己臉上了。

于是乎,現在就沒有直接暴露的API了。

上面代碼的demo位址應該不會被人看到吧……

6. 我不喜歡new, 我喜歡$

new F("image")

這種寫法我好不喜歡,我喜歡

$

, 我就是喜歡

$

, 我要換掉。

好吧,把

new

什麼什麼藏在

$

方法中把~

var $ = function(id) {
    return new F(id);
};      

于是,上面的圖檔隐藏的直接執行代碼就是:

$("image").hide();      

上面代碼的demo位址應該不會被人看到吧……

IE6浏覽器也是支援的哦!是不是已經有些jQuery的樣子啦!

7. 你怎麼就一種姿勢啊,人家都膩了诶

循序漸進到現在,都是拿

id

來舉例的,實際應用,我們可能要使用類名啊,标簽名啊什麼的,現在,為了接下來的繼續,有必要支援多個“姿勢”。

在IE8+浏覽器中,我們有選擇器API,

document.querySelector

document.querySelectorAll

,前者傳回唯一

Node

,後者為

NodeList

集合。大統一起見,我們使用後者。于是,就有:

var F = function(selector, context) {
    return this.getNodeList(selector, context);
};
F.prototype.getNodeList = function(selector, context) {
    context = context || document;
    this.element = context.querySelectorAll(selector);
    return this;
};

var $ = function(selector, context) {
    return new F(selector, context);
};      

此時,我們就可以使用各種選擇器了,例如,

$("body #image")

this.element

就是選擇的元素們。

8. IE6/IE7腫麼辦?

IE6/IE7不認識

querySelectorAll

,咋辦?

[轉載]jQuery誕生記-原理與機制

jQuery就使用了一個比較強大的選擇器架構-

Sizzle

. 知道就好,重在示範原理,是以,下面還是使用原生的選擇器API示意,故demo效果需要IE8+浏覽器下檢視。

8. 周遊是個麻煩事

this.element

此時類型是

NodeList

, 是以,直接

this.element.style.xxx

的做法一定是報錯,看來有必要循環下:

F.prototype.hide = function() {
    var i=0, length = this.element.length;
    for (; i<length; i+=1) {
        this.element[i].style.display = "none";
    }    
};      

于是乎:

$("img").hide();  // 頁面所有圖檔都隐藏啦!      

上面代碼的demo位址應該不會被人看到吧……

單純一個

hide

方法還可以應付,再來個

show

方法,豈不是還要循環周遊一次,豈不是要煩死~ 

[轉載]jQuery誕生記-原理與機制

是以,急需一個周遊包裝器元素的方法,姑且叫做

each

吧~

于是有:

F.prototype.each = function(fn) {
    var i=0, length = this.element.length;
    for (; i<length; i+=1) {
        fn.call(this.element[i], i, this.element[i]);
    }
    return this;
};
F.prototype.hide = function() {
    this.each(function() {
       this.style.display = "none";
    });
};

$("img").hide();  // 頁面所有圖檔都隐藏啦!      

上面代碼的demo位址應該不會被人看到吧……

9. 我不喜歡this.element, 可以去掉嗎?

現在包裝器對象結構類似這樣:

F.prototype = {
    element: [NodeList],
    each: function() {},
    hide: function() {}
}      

element

看上去好礙眼,就不能去掉嗎?可以啊,寶貝,

NodeList

是個類數組結構,我們把它以數值索引形式配置設定到對象中就好啦!一來去除備援

element

屬性,二來讓原型對象成為類數組結構,可以有一些特殊的功能。

于是,

F.prototype.getNodeList

需要換一個名字了,比方說初始化

init

, 于是有:

F.prototype.init = function(selector, context) {
    var nodeList = (context || document).querySelectorAll(selector);
    this.length = nodeList.length;
    for (var i=0; i<this.length; i+=1) {
        this[i] = nodeList[i];    
    }
    return this;
};      

此時,

each

方法中,就沒有煩人礙眼的

this.element[i]

出現了,而是直接的

this[i]

.

F.prototype.each = function(fn) {
    var i=0, length = this.length;
    for (; i<length; i+=1) {
        fn.call(this[i], i, this[i]);
    }
    return this;
};      

我們也可以直接使用索引通路包裝器中的DOM元素。例如:

$("img")[0]

就是第一張圖檔啦!

上面代碼的demo位址應該不會被人看到吧……

10. 我是完美主義者,我特不喜歡F名稱,可以換掉嗎?

F

這個名稱從頭到尾出現,我好不喜歡的來,我要換成

$

, 我就是要換成

$

符号……

這個……

$

已經用了啊,再用沖突的吧。再說,你又不是狐後,耍無賴也沒用啊……

[轉載]jQuery誕生記-原理與機制
[轉載]jQuery誕生記-原理與機制
[轉載]jQuery誕生記-原理與機制

 好吧,想想其他辦法吧。一步一步來,那我把所有的

F

換成

$.fn

.

就有:

[轉載]jQuery誕生記-原理與機制

上圖代碼的demo位址應該不會被人看到吧……

顯然,運作是OK的。似乎也非常有jQuery的模樣了,但是,實際上,跟jQuery比還是有差别的,有個較大的差别。如果是上圖代碼所示的JS結構,則包裝器對象要擴充新方法,每個都需要再寫一個原型的。例如,擴充一個

attr

方法,則要寫成:

$.fn.prototype.attr = function() {
    // ...
};      

又看到

prototype

了,進階的東西應該要隐藏住,否則會給人難以上手的感覺。那該怎麼辦呢?禦姐不是好惹的。

腦子動一下就知道了,把

F.prototype

換成

$.fn

不久好了。這樣,擴充新方法的時候,直接就是

$.fn.attr = function() {
    // ...
};      

至此,就使用上講,與jQuery非常接近了。 但是,還有幾個

F

怎麼辦呢,總不能就像下面這樣放着吧:

var $ = function(selector, context) {
    return new F(selector, context);
};
var F = function(selector, context) {
    return this.init(selector, context);
};

$.fn = F.prototype;

$.fn.init = function(selector, context) {
    // ...
    return this;
};
$.fn.each = function(fn) {
   // ...
};
$.fn.hide = function() {
   // ...
};      

數學中,我們都學過合并同類項。仔細觀察上面的的代碼:

$()

傳回的是

new F()

,而

new F()

又是傳回的對象的引用。擦,這傳回來傳回去的,參數又是一樣的,我們是不是可以一次性傳回,然後再做些手腳,讓

$.fn.init

傳回的

this

依然能夠正确指向。

于是,一番調整有:

var $ = function(selector, context) {
    return new $.fn.init(selector, context);
};
var F = function() { };

$.fn = F.prototype;
$.fn.init = function(selector, context) {
    // ...
    return this;
};

// ...      

上面代碼顯然是有問題的,

new

的是

$.fn.init

$.fn.init

的傳回值是

this

. 也就是

$()

的傳回值是

$.fn.init

的原型對象,尼瑪

$.fn.init

prototype

原型現在就是個光杆司令啊,喲,正好,

$.fn

對應的原型方法,除了init沒用外,其他

hide()

each()

就是我們需要的。是以,我們需要加上這麼一行:

$.fn.init.prototype = $.fn      

于是,

$()

的傳回值從

$.fn.init.prototype

一下子變成

$.fn

,正好就是我們一開始的擴充方法。

于是乎,大功告成。慢着……

[轉載]jQuery誕生記-原理與機制

上面明明還有殘留的

F

呢!

[轉載]jQuery誕生記-原理與機制

 哦,那個啊。

F

是任意函數,

$

本身就是函數,是以,直接使用

$

替換就可以了:

var $ = function(selector, context) {
    return new $.fn.init(selector, context);
};
var F = function() { };   // 這個直接删除
$.fn = $.prototype;
$.fn.init = function(selector, context) {
    // ...
    return this;
};

// ...      

上圖代碼的demo位址應該不會被人看到吧……

實際上,如果你不是非得一個

$

行便天下的話,到了上面進階第9步就足夠了。jQuery在第10步的處理是為了彰顯其

$

用得如此的出神入化,代碼完美,令人驚歎!

至此,jQuery大核心已經一步一步走完了,可以看到,所有的這些進階都是根據需求、實際開發需要來的,慢慢完善,慢慢擴充的!

11. 每個擴充方法都要$.fn.xxxx, 好鬧心的來

$.fn.css = function() {}
$.fn.attr = function() {}
$.fn.data = function() {}
// ...      

每個擴充前面都有個

$.fn

, 好讨厭的感覺,就不能合并嗎?

于是,jQuery搞了個

extend

方法。

$.fn.extend({
    css: function() {},
    attr: function() {},
    data: function() {},
    // ...
});      

12. $()不僅可以是選擇器字元串,還可以是DOM

init

方法中,判斷第一個參數,如果是節點,直接

this[0] = this_node

. over!

以下13~?都是完善啊,補充啊,相容性處理啊什麼的,沒有價值,到此為止!

三、排了很長隊的結束語

網上也有其他一些介紹jQuery原理或機制的文章,可能當事人自己了解,而閱讀者本來就不懂,說來說去,越說越繞,可能更不懂了。

jQuery是很優秀,好比身為靈長類的人類。但是,其誕生顯然是從簡單開始的。是以,要了解人類,可以通過追溯其起源。如果你是上帝,要讓你造一個人,你會怎麼造,是一口氣出來?女娲造人還要捏泥人呢!不妨從單細胞生物開始,随着自然進化,淘汰,自然而然,就會出現人類,上帝他就是這麼幹的。

jQuery的誕生也大緻如此,要想了解jQuery,可以試試踏着本文jQuery的成長足迹,一點一點逐漸深入,您就會了解為何jQuery要這麼設計,它是如何設計的等。

雖然,内容由淺及深,但是,其中涉及的原型以及

new

構造函數的一些特性,對于新人而言,還是有一些了解門檻的,希望我的描述與解釋可以讓你有一絲豁然開朗,那就再好不過了。

感謝您的閱讀至此,歡迎指出文章可能書寫不準确的地方,再次感謝!

月底在百姓網有個小分享,示範文檔連個肉渣子還沒準備呢。是以,未來一周休文。

原創文章,轉載請注明來自張鑫旭-鑫空間-鑫生活[http://www.zhangxinxu.com]

本文位址:http://www.zhangxinxu.com/wordpress/?p=3520

轉載于:https://www.cnblogs.com/mrzzcn/p/jQuery1.html