自己寫個jQuery
之前寫過一篇JavaScript名稱空間的文章,寫完後一對比對jQuery的簡單使用很是驚羨,看了看人家原始碼,用的原理很類似啊,改進一下之前的版本,做個簡易版的jQuery
之前的程式碼
(function () { var _NS = function () { } _NS.prototype.select = function (selector,context) { var context = context || document;return context.querySelectorAll(selector); } _NS.prototype.isArrayLike=function(obj){ if(obj instanceof Array){ return true; } var length=obj.length; if ( obj.nodeType === 1 && length ) {return true; } return false; } _NS.prototype.html = function (obj,value) { var isArray=this.isArrayLike(obj), i=0; if (typeof value == 'string') { if (!isArray) { obj.innerHTML= value; } else { var length = obj.length; while (i < length) { obj[i].innerHTML = value; i += 1; } } } else { if (!isArray) { return obj.innerHTML; } else { return obj[0].innerHTML; } } } window.NS = new _NS(); })();
這樣的寫法只是對各種自定義方法的隔離,只是能用而已,不支援日趨流行的鏈式呼叫,使用jQuery的時候可以很方便的寫出$(selector).xxx().xxxx().xxxxxx() 這樣格式的程式碼,既簡潔易讀效率又高,而上面的寫法只能呼叫一個個孤零零函式,沒有物件整體性可言。
上面的select方法返回值為查詢的物件(IE低版本瀏覽器不支援),很多時候獲取一個物件後我們希望使用庫函式對其直接進行操作,比如我們希望把頁面上所有div的innerHTML設為test,並隱藏這些div,如果用jQuery會這麼寫
$(div).html('test').css(‘display’,’none’);
上面的程式碼雖然沒實現css方法,但是如果有的話得這麼寫
var divs=NS.select('div'); divs.html('test'); divs.css('display','none');
為什麼jQuery很方便
jQuery好用有幾個原因:
1. $本身是個function物件,包含一些“靜態方法”(不用例項化就可以用的方法),比如$.ajax、$.animation,可以這樣$.xxx()直接使用jQuery的一些庫函式
2. 因為$本身是一個函式,可以被呼叫。但是$(selector) 返回結果並不是搜尋的結果集,而是一個jQuery例項,結果集被封裝在jQuery物件內,這樣可以使用一些jQUery的例項方法(也就是定義在prototype內的方法等),例如$(‘div’).html(‘test’), 這樣由於$(‘div’)返回的是jQuery例項,所以可以呼叫例項方法html()。
3. jQuery物件大部分例項方法儘量返回jQuery物件,即呼叫者本身,這樣可以支援鏈式呼叫,比如$(‘div’).html(‘test’).css(‘display’,’none’) , $(‘div’)返回jQuery物件,裡面包含結果集,呼叫例項方法html(‘test’) 同樣返回jQuery物件,呼叫 css(‘display’,’none’) 同樣也返回jQuery物件,可以這樣一直呼叫下去。
建構函式的一些知識
想要做到上面幾點除了prototype等基本知識,還需要了解一些關於JavaScript建構函式的知識。
1.什麼樣的函式是建構函式
在JavaScript的世界裡建構函式並不神祕,也不特殊,任何函式通過new 操作符呼叫都可以變為建構函式,不使用new 操作符就不是建構函式,而是直接按普通函式呼叫。
2.建構函式返回什麼樣的結果
建構函式的返回值分為兩種情況,當function沒有return語句或者return回一個基本型別(bool,int,string,undefined,null)的時候,返回new 建立的一個匿名物件,該物件即為函式例項;如果function體內return一個引用型別物件(Array,Function,Object等)時,該物件會覆蓋new建立的匿名物件作為返回值。
寫個小例子驗證一下
function A(){ return true; } var a=new A(); console.log(a instanceof A); //true function B(){ return new Array(); } var b=new B(); console.log(b instanceof Array); //true
做個貧下中農版的jQuery
針對上面講的jQuery的幾點好處,嘗試寫一個版本,為了更像jQuery一些,把返回函式名字也改為$,先寫一個框架
version 0.1
(function(){ var $=function(selector,context){ }; $.ajax=function(configs){ //靜態方法 //TODO } $.prototype.html=function(value){ //例項方法 //TODO } window.$=$; })();
關於靜態方法和例項方法的部分很好實現,就寫成類似version 0.1的樣子就行,在這個版本中$確實是個函數了,但是怎麼讓$(selector)執行返回$的例項,同時例項內又包含搜尋結果。嘗試寫下一個版本
version0.2
(function(){ var $=function(selector,context){ var context = context || document; var nodeList = context.querySelectorAll(selector); var $=new $(); $.elements=nodeList; return $; }; $.ajax=function(configs){ //靜態方法 //TODO } $.prototype.html=function(value){ //例項方法 //TODO } window.$=$; })();
這個看起來可以,$是個函式,有一些靜態方法,$(selector)返回$例項,包含搜尋結果集,但是這種方式有語法上的錯誤,程式碼中試圖在$function體內new自己,在執行到new的時候JavaScript還不認識$,失敗!
可以嘗試換一個思路 version 0.3
(function(){ function f(selector,context){ return ?; } var $=(function(){ return f; })(); window.$=$; })();
這樣$同樣也是個函式,但是怎麼才能讓其執行結果返回本身的例項呢,也就是f到底應該怎樣返回$的例項呢,說了這麼多遍例項終於想起除了直接new一個物件可以得到其例項,還有一個地方可以得到其例項,在prototype中定義的函式可以訪問this物件並返回。這就要求在f的prototype函式內返回this,不斷的改啊調啊終於成了這樣
version 0.4
(function(){ var $=(function(){ function f(selector,context){ return f.prototype.init(selector,context); } f.prototype.init=function(selector,context){ var context = context || document; var nodeList = context.querySelectorAll(selector); this.length = nodeList.length; this.elements=new Array(); for (var i = 0; i < this.length; i++) { this.elements[i] = nodeList[i]; } return this; } return f; })(); window.$=$; })();
填上剛才自定義的函式
version 1.0
(function(){ var $=(function(){ function f(selector,context){ return f.prototype.init(selector,context); } f.ajax=function(configs){ //TODO } f.prototype.init=function(selector,context){ var context = context || document; var nodeList = context.querySelectorAll(selector); this.length = nodeList.length; this.elements=new Array(); for (var i = 0; i < this.length; i++) { this.elements[i] = nodeList[i]; } , return this; } f.prototype.html=function(value){ //TODO } return f; })(); window.$=$; })();
這樣終於所有要求都實現了,在內部匿名函式中定義function f,最後返回賦值給$,這樣$是個函式,在執行的時候層層呼叫,最後呼叫到f.prototype.init,並返回其返回物件(好繞口),在init中把搜尋結果放到this的屬性中,最後返回this,然後f在把this返回,這樣$(selector)的結果是$物件例項,而且包含搜尋結果。
jQuery原始碼結構
上面的結果已經很讓人滿意了,仔細讀了讀jQuery原始碼,看看jQuery結構
(function( window, undefined ) { var jQuery = (function() { // 構建jQuery物件 var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); } // jQuery物件原型 jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { // selector有以下7種分支情況: // DOM元素 // body(優化) // 字串:HTML標籤、HTML字串、#id、選擇器表示式 // 函式(作為ready回撥函式) // 最後返回偽陣列 } }; //把jQuery的prototype賦值給init方法的prototype jQuery.fn.init.prototype = jQuery.fn; // 合併內容到第一個引數中,後續大部分功能都通過該函式擴充套件 // 通過jQuery.fn.extend擴充套件的函式,大部分都會呼叫通過jQuery.extend擴充套件的同名函式 jQuery.extend = jQuery.fn.extend = function() {}; // 在jQuery上擴充套件靜態方法 jQuery.extend({ // ready bindReady // isPlainObject isEmptyObject // parseJSON parseXML // globalEval // each makeArray inArray merge grep map // proxy // access // uaMatch // sub // browser }); return jQuery; })(); window.jQuery = window.$ = jQuery; })(window);
總體上是一致的,但是jQuery的結構要科學很多
1.將window物件傳入匿名函式,使匿名函式內部可以直接訪問,防止匿名函式內部使用window物件的時候需要層層查詢作用域鏈,最後才能找到window
2. 沒有一棍子打死,完全使用$,當出現$命名衝突的時候可以使用jQuery代替
3. 定義jQuery.fn=jQuery.prototype,程式碼寫起來方便了很多,也有利於壓縮
4. 沒有使用elements屬性,而是利用陣列特性封裝搜尋結果集,在使用的時候更容易想到
5. 定義each函式用於遍歷結果集
6. 提供extend函式用於向物件內部新增屬性
窮人版jQuery Version2.0
看了大師的寫法終於可以脫離貧下中農了
(function () { var $ = (function () { var $ = function (selector, context) { return new $.prototype.init(selector, context); } $.prototype.init = function (selector, context) { var context = context || document; var nodeList = context.querySelectorAll(selector); this.length = nodeList.length; for (var i = 0; i < this.length; i++) { this[i] = nodeList[i]; } return this; } $.prototype.each = function (callback, args) { var length = this.length, i = 0; if (args) { while (i < length) { callback.call(this[i], args); i += 1; } } else { while (i < length) { callback.call(this[i]); i += 1; } } return this; } $.prototype.html = function (value) { if (typeof value == 'string') { this.each(function () { this.innerHTML = value; }); return this; } else { return this[0].innerHTML; } } $.prototype.init.prototype = $.prototype; return $; })(); window.$ = $; })();