本文簡單實現jQuery框架,深刻理解javascript對象。
本文的對照版本是jQuery-1.2.6.jsjavascript
本文注重jquery結構設計思路,並不側重具體功能的實現以及兼容性和安全性的部分。css
首先創建基本框架以下:html
(function(window){ "use strict"; var jQuery = window.jQuery = window.$ = function(selector){ //定義$函數,並把$和jQuery暴露到外面 }; jQuery.fn = jQuery.prototype = { //jQuery原型 }; jQuery.extend = jQuery.fn.extend = function(){ //添加擴展方法,jQuery.extend添加靜態方法,也能夠實現繼承,jQuery.fn.extend添加動態方法 } })(window);
進一步,實現jQuery的初始化java
//上述框架中的部分代碼 //因爲$('#selector')獲得的是一個jQuery對象,嘗試直接返回jQuery對象 var jQuery = window.jQuery = window.$ = function(selector){ return new jQuery(); //這裏會致使一個死循環,因此不能這樣直接構建jQuery對象 };
修正上述代碼中的死循環,咱們能夠試圖返回this,可是this明顯是window,不是咱們須要的jQuery,利用原型中的this返回構造函數實例化對象的特色(不理解的能夠參看javascript中this詳解),咱們做如下修改:jquery
//上述框架中的部分代碼 //因爲$('#selector')獲得的是一個jQuery對象,嘗試直接返回jQuery對象 var jQuery = window.jQuery = window.$ = function(){ return jQuery.fn.init(); //執行初始化 }; jQuery.fn = jQuery.prototype = { init: function(){ return this; }, jQuery: "1.0.0", //jQuery版本信息 length: 0, //模擬數組,即這裏構成一個類數組對象 size: function(){ return this.length; } }
到此$()能夠返回一個jQuery對象了。可是在舊瀏覽器中有一個bug。若是用戶以下這樣使用代碼,那麼什麼都得不到:segmentfault
var ele = $.fn.init();
這裏直接調用了init()
, 這樣會獲得init創造的對象,因爲$
是個函數,$.fn
是函數的原型,函數原型是個空函數,因此這裏獲得了一個以空函數爲構造函數創造的對象。爲了解決這問題,採用new的方式:數組
//上述框架中的部分代碼 var jQuery = window.jQuery = window.$ = function(selector){ return new jQuery.fn.init(selector); //執行初始化 }; jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function(selector){ var elements = document.querySelectorAll(selector); //順便簡單的實現了選擇器 //注意querySelectorAll在老的瀏覽器中是不支持的,這裏專一在jQuery的結構上。 Array.prototype.push.apply(this, elements); //構成類數組對象,引入length,並使其自增 return this; }, jQuery: "1.0.0", //jQuery版本信息 length: 0, //模擬數組,即這裏構成一個類數組對象 size: function(){ return this.length; } } jQuery.fn.init.prototype = jQuery.fn;
因爲這裏把jQuery.fn.init()做爲構造函數調用,獲得一個jQuery對象,因此咱們把jQuery.fn做爲jQuery.fn.init()的原型。瀏覽器
下一步實現extend方法安全
//上述框架中的部分代碼 jQuery.extend = jQuery.fn.extend = function(obj, srcObj){ var target, len = arguments.length; if(len === 1){ //傳入一個參數時實現繼承 deep(obj, this); return this; } else { for(var i = 1; i < len; i++){ target = arguments[i]; deep(target, obj); } return obj; } function deep(oldOne, newOne){ //實現深拷貝 for(var prop in oldOne){ if(typeof oldOne[prop] === "object" && oldOne[prop] !== null){ newOne[prop] = oldOne[prop].constructor === Array ? [] : {}; deep(oldOne[prop], newOne[prop]); } else{ newOne[prop] = oldOne[prop]; } } } };
寫了extend,咱們定義幾個簡單的方法(2靜態方法,3個動態方法)能夠用來測試。app
//添加靜態方法 jQuery.extend({ trim: function(text){ return (text || "").replace(/^\s+|\s+$/g, ""); }, makeArray: function(obj){ return Array.prototype.slice.call(obj); } }); //添加動態方法 jQuery.fn.extend({ //get方法 get: function(num){ return num == null ? jQuery.makeArray(this): num < 0 ? this[ num + this.length ] : this[ num ]; //索引小於零表示倒數 }, //each 遍歷執行函數 each: function(fun){ for(var i = 0, len = this.length; i < len; ++i){ if(fun(i, this[i]) === false) break; } return this; //用於鏈式調用 }, //修改css屬性 css: function(key, value){ var len = arguments.length; if(len === 1){ //傳入1個參數返回對應值 return this[0].style[key]; } else if(len === 2){ //傳入2個參數設置對應值 this.each(function(index, ele){ ele.style[key] = value; }); } return this; //用於鏈式調用 } });
到這裏,jQuery的基本結構就造成了,還有一個問題須要解決,就是處理變量衝突。
當環境中以及有jQuery
和$
時能夠選擇釋放$
或時釋放jQuery
和$
(function(window){ "use strict"; //在框架一開始先保留外部可能存在的$或jQuery變量,以便在後來恢復 var _$ = window.$; var _jQuery = window.jQuery; var jQuery = window.jQuery = window.$ = function(selector){}; jQuery.fn = jQuery.prototype = {}; jQuery.extend = jQuery.fn.extend = function(){}; })(window);
而後寫noConflict函數
jQuery.extend({ noConflict: function(deep){ //傳入true時同時釋放$和jQuery,不然只是釋放$ window.$ = _$; if(deep) window.jQuery = _jQuery; return jQuery; } });
到此爲止,jQuery的框架已經造成。下面是完整代碼部分:
(function(window){ "use strict"; var _$ = window.$; var _jQuery = window.jQuery; //定義全局接口 var jQuery = window.jQuery = window.$ = function(selector){ return new jQuery.fn.init(selector); }; var HTMLRegex = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/; //用於匹配html標籤 var rootjQuery; //默認根節點的jQuery對象 //原型 jQuery.fn = jQuery.prototype = { constructor: jQuery, // init: function(selector){ // var elements = document.getElementsByTagName(selector); // Array.prototype.push.apply(this, elements); // return this; // }, init: function(selector){ if(!selector){ return this; } var elements = document.getElementsByTagName(selector); Array.prototype.push.apply(this, elements); return this; }, jQuery: "1.0.0", length: 0, size: function(){ return this.length; } }; jQuery.fn.init.prototype = jQuery.fn; //繼承 jQuery.extend = jQuery.fn.extend = function(obj){ var target, len = arguments.length; if(len === 1){ //傳入一個參數時實現繼承 deep(obj, this); return this; } else { for(var i = 1; i < len; i++){ target = arguments[i]; deep(target, obj); } return obj; } function deep(oldOne, newOne){ for(var prop in oldOne){ if(typeof oldOne[prop] === "object" && oldOne[prop] !== null){ newOne[prop] = oldOne[prop].constructor === Array ? [] : {}; deep(oldOne[prop], newOne[prop]); } else{ newOne[prop] = oldOne[prop]; } } } }; //靜態函數 jQuery.extend({ trim: function(text){ return (text || "").replace(/^\s+|\s+$/g, ""); }, noConflict: function(deep){ window.$ = _$; if(deep) window.jQuery = _jQuery; return jQuery; }, makeArray: function(obj){ return Array.prototype.slice.call(obj); } }); //對象方法 jQuery.fn.extend({ get: function(num){ return num == null ? jQuery.makeArray(this): num < 0 ? this[ num + this.length ] : this[ num ]; //索引小於零表示倒數第n個 }, each: function(fun){ for(var i = 0, len = this.length; i < len; ++i){ if(fun(i, this[i]) === false) break; } return this; //用於鏈式調用 }, css: function(key, value){ var len = arguments.length; if(len === 1){ return this[0].style[key]; } else if(len === 2){ this.each(function(index, ele){ ele.style[key] = value; }); } return this; //用於鏈式調用 } }); }(window));
上述代碼源碼:Download