从零开始写相似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

从新整理排版便于查看。

相关文章
相关标签/搜索