從零開始寫相似jQuery的Javascript框架

      隨着時代發展,javascript陣營裏面出現了愈來愈多的優秀的框架,大大簡化了咱們的開發工做,在咱們使用這些框架的時候是否是也應該飲水思源想一想它們都是怎樣構建起來的呢?若是你不知足於僅僅是使用一些現成的API,而是深刻了解它們內部的實現機制(照某人的說法, API是貶值最快的東西),最好的辦法就是閱讀它們的源代碼了,前提是你讀得懂。javascript

        最近兩天研究了一下jQuery的源碼,在這裏將本人一些粗淺認識分享出來,不當之處請各位指正。好了,下面咱們就來看看jQuery大概是怎樣工做的,我假定你已經具有了一些基本的javascript知識,若是基礎不夠俺推薦你閱讀《JavaScript高級程序設計》和《悟透JavaScript》這兩本書。本文不適合對js裏面的類、對象、函數、prototype等概念沒有了解的朋友。html

咱們從最開始的提及:java

首先構造一個對象給使用者,假定咱們這個框架叫 Shaka   ( 俺的名字;) )
var Shaka = function(){}; 這裏咱們建立了一個空函數,裏面什麼也沒有,這個函數實際上就是咱們的構造函數。爲了讓咱們生成的對象可以調用在prototype裏定義出來的方法, 咱們須要用原型的方式(把Shaka看成是一個類)給Shaka添加一些方法,因而定義:jquery

Shaka.fn =  Shaka.prototype = {};

這裏的Shaka.fn至關於Shaka.prototype的別名,方便之後使用,它們指向同一個引用。web

OK,咱們添加一個sayHello的方法, 給Shaka添加一個參數,這樣這個框架最基本的樣子已經有了,若是它有生命的話那麼它如今是1歲, 看代碼:ajax

var Shaka = function(age){
    this.age = age;
};
Shaka.fn = Shaka.prototype = {
    sayHello: function() {
         alert('I am a little baby, my age is ' + this.age + ' years old.');
    }
};
var babyShaka = new Shaka(1);
babyShaka.sayHello();

好啦,先別激動, 咱們注意到這個框架跟jQuery在使用上是有一些差異的, 好比在jq 中咱們能夠這樣寫:框架

jQuery('#myid').someMethod();

這是怎樣作到的呢, 也就是說 jQuery()這個構造函數返回了一個jQuery的對象實例,所以咱們能夠在上面調用它的方法,因此Shaka的構造函數應該返回一個實例,它看起來應該是這個樣子:函數

var Shaka = function(){ return //返回Shaka的實例; };

那麼咱們要如何取得一個Shaka的實例呢, 咱們先來回顧一下使用prototype方式來模擬類的時候 var someObj = new  MyClass(); 這個時候其實是建立一個新對象someObje,把新對象做爲this指針,調用 MyClass函數,即類的構造函數, 而後 someObj 就得到了在 MyClass.prototype裏面定義的方法, 這些方法內的this指針指當前對象實例。

在jQuery中使用了一個工廠方法來建立一個實例,這個方法位於jQuery.prototype中, 如今咱們從新來定義Shaka.prototype, 給它添加一個init方法用於返回一個Shaka的實例, 而且把Shaka的構造函數稍稍改變一下:post

var Shaka = function(age) {
    return new Shaka.fn.init(age);
};

Shaka.fn = Shaka.prototype = { 
    init: function(age) {
        this.age = age; return this;
    },
    sayHello: function() {
        alert('I am a little baby, my age is ' + this.age + ' years old.');
    }
};

Shaka.fn.init.prototype = Shaka.fn;

//這裏new Shaka.fn.init(age)建立的對象具備init方法的prototype指向對象的方法 , 所以咱們將init方法的prototype指向 Shaka的prototype, 這樣建立出來的對象就具備了Shaka.prototype裏面定義的方法。測試


OK,如今咱們的小寶寶變成大一點的寶寶了,打個招呼先:

var Shaka = function(age) {
    return new Shaka.fn.init(age);
};
Shaka.fn = Shaka.prototype = {
    init: function(age) {
        this.age = age; return this;
    },
    sayHello: function() {
        alert('I am a little big baby, my age is ' + this.age + ' years old.');
    }
};
Shaka.fn.init.prototype = Shaka.fn;
Shaka(2).sayHello();

嗯,好象有點樣子了,可是光這樣還不行,來點實際的, 咱們在新框架中實現jquery裏val()方法的部分功能,這個方法不加參數調用時返回指定ID的input的值,加參數時爲設定這個input的值,與jQery同樣,咱們約定使用id來查找對象時使用"#"符號。把要查找的目標ID做爲構造函數的參數傳進去,咱們給Shaka.prototype添加一個val()方法, 給Shaka添加一個selector的屬性用於存儲咱們要查找的目標。

Shaka.fn = Shaka.prototype = { 
    init: function(selector) {
        this.selector = selector; return this;
    },
    val: function(newValue) { //方法實現代碼 }
};
var Shaka = function(selector) {
    return new Shaka.fn.init(selector);
};

<form method="post" action="" name="myform">
我幾歲了? <br />
<input id="myInput"  type="text" value="Hello world!" size="50" />
</form>
<script type="text/javascript">
var Shaka = function(selector) {
    return new Shaka.fn.init(selector);
};
Shaka.fn = Shaka.prototype = {
    init: function(selector) {
        if(selector) this.selector = selector; return this;
    },
    val: function(newValue) {
        //start val function body
        if(!(this.selector && this.selector.indexOf('#') == 0 && this.selector.length != 1))
            return; //簡單地判斷傳入值非法, 最好使用正則
        var id = this.selector.substring(1);
        var obj = document.getElementById(id);
        if(obj)//若是對象存在
        {
            if(newValue == undefined)
            return obj.value;//獲取目標對象的值.
            obj.value = newValue;// 將目標對象的value屬性設置爲newValue.
            return this; //爲了使方法能夠連續調用。
        }
    }
};
Shaka.fn.init.prototype = Shaka.fn;
alert('object old value is '+Shaka('#myInput').val());
alert(Shaka('#myInput').val('I am 3 years old now!').val());
</script>

到目前爲止咱們已經建立一個能夠工做的框架雛形,爲了使程序能夠更方便地被調用,好比jQuery可使用$符號來簡寫,咱們也弄一個,在此以前咱們先來回顧兩個東西:

1. 咱們在腳本中能夠這樣定義變量:

var foo = 'someThing'; 
bar = 'otherthing';

這樣兩種寫法都是合法的,可是意義徹底不一樣, 第一個語句建立了一個新的變量,而第二個是定義了window對象的一個屬性,至關於window.bar = 'otherthing';, 所以咱們想使咱們的Shaka具備這樣的調用方式能力: $.someMethod();就須要將Shaka設置爲window的一個屬性, 因而咱們的Shaka構造函數就得寫成這樣

var Shaka = window.Shaka = window.$ = function(selector) {
    return new Shaka.fn.init(selector);
};

2. javascript的匿名函數.

建立並執行一個匿名函數的基本形式: (function(){ alert('Hello World!'); })(); 爲何要用到匿名函數呢,由於咱們不想把Shaka的內部實現暴露出來,這樣容易與其它代碼衝突,只提供一個單一的入口,咱們能夠這樣測試一下:

(function(){
    function privateFunction(){
        alert('You can not see me, haha');
    };
})();

function publicMethod() {
    alert('I am public');
};
alert('匿名函數內部的函數是不可訪問的, privateMethod 目前是: ' + typeof privateMethod);
alert('全局函數可訪, publicMethod 目前是: ' + typeof publicMethod);

而後,還有一個問題須要解決,俺們的框架作出來了可是還很簡陋,在這以前咱們須要讓它與其它的框架協同工做,所以帶來一個問題, 若是咱們都使用$做爲簡寫形式就會衝突了, 象jQuery同樣,咱們須要提供一個noConfilit的方法「出讓」$的使用權。在咱們的程序最開始處加入下面的代碼:

var _$ = window.$;

意思是將此前定義的$對象引用放到 _$ 中, 而後咱們再給Shaka擴展一個方法出來, 若是其它開發者須要自行擴展的話也可使用這個方式(jQuery的extend方法提供了更爲強大的功能,請你們自行研究):

(function($){
    //extend method definition. 
})(Shaka);

意思是將Shaka做爲這個匿名函數的參數來調用這個方法。
前面咱們講過 Shaka.fn 就是 Shaka.prototype 的別名,所以咱們要在Shaka.prototype 裏面添加新的方法就能夠寫成這樣

(function($){ 
    $.fn.noConflict = function(){ 
        window.$ = _$;//把$還給在開始處取得的引用.
    };
})(Shaka);

如今咱們來看一個完整的:

<form method="post" action="" name="myform">
    <h1> 我幾歲了?</h1> <br />
    <input id="myInput"  type="text" value="Hello world!" size="50" />
    <input id="otherInput"  type="text" size="50" />
</form>

<script type="text/javascript">
//咱們在這裏模擬一下在這以前若是加載了其它框架的情形, 這個時候window.$不爲空.
window.$ = {
    whoAmI: function(){
	    alert('This function result is from other js lib.');
	}
};

(function(){
    // 建立最外層匿名函數.
    window._$ = window.$;//將別的框架定義的$暫存.
    //給Shaka加上$ 的別名.
    var Shaka = window.Shaka = window.$ = function(selector) {
	    return new Shaka.fn.init(selector);
	};
    Shaka.fn = Shaka.prototype = {
        init: function(selector) {
		    if(selector) this.selector = selector; return this;
		},
        val: function(newValue) {
            //start val function body
            if(!(this.selector && this.selector.indexOf('#') == 0 && this.selector.length != 1))
            return; //簡單地判斷傳入值非法, 最好使用正則
            var id = this.selector.substring(1);
            var obj = document.getElementById(id);
            if(obj)//若是對象存在
            {
                if(newValue == undefined)
                return obj.value; //獲取目標對象的值.
                obj.value = newValue; //將目標對象的value屬性設置爲newValue.
                return this; //爲了使方法能夠連續調用, 返回當前實例。
            }
        }
    };
    Shaka.fn.init.prototype = Shaka.fn;
})();

//擴展新的方法.
(function($){
    //alert(obj.fn);
    $.noConflict = function(){
        window.$ = window._$; //把$還給在開始處取得的引用.
    };
})(Shaka);

//若是沒有引入其它的框架,能夠這麼寫
//alert('object old value is '+$('#myInput').val());
//alert($('#myInput').val('I am 3 years now!').val());

//強制使用完整名稱.
Shaka.noConflict();
alert('object old value is '+Shaka('#myInput').val());
alert(Shaka('#myInput').val('I am 5 years old now!').val());
//Shaka('#otherInput').val('這裏的值是使用Shaka(\'#otherInput\').val()方法來寫入的哦');

//或者能夠這樣寫也行,仍然使用$, 把Shaka做爲匿名函數的參數$傳進去。
(function($){
    //又能夠用$了, 哈哈
    $('#otherInput').val('這裏的值是使用Shaka(\'#otherInput\').val()方法來寫入的哦');
})(Shaka);

//如今仍然可使用$調用其它框架的方法.
$.whoAmI();
</script>

完結! 

本文轉至: http://www.jzxue.com/wangzhankaifa/javascript-ajax/201001/11-3402_3.html

從新整理排版便於查看。

相關文章
相關標籤/搜索