這篇文章是分析jQuery是如何封裝的。這裏把我本身模擬jQuery封裝的一個類庫拿出來分享。css
1.這篇文章能夠看作是我以前的一篇博文 淺析jQuery基本原理($實現原理)的續篇
html
2.我的認爲jQuery 與其餘庫相比,它有3個最大的特色,其一是獨有的jQuery對象,其二是隱式迭代,其三是鏈式編程。前端
3.因此我所封裝的庫,重點就在於描述jQuery的這3個特徵是如何實現的,而不是真的要作一個完美的庫,咱們要說的是思想,是方法,而不是講項目。ajax
4.牛頓說他之因此看得遠,是由於站在了巨人的肩膀上。而jQuery就是咱們學習前端的一個巨人,咱們應該拿它的源碼來學習下。編程
代碼中有一些必要的說明。不過不能說足夠,主要是有點多,我懶得再作說明了。這裏簡單的提一點,jQuery.extend是一個核心模塊,這個模塊是用來拓展jQuery其餘api的。換句話說,其餘的api都不是直接就寫到jQuery對象或者其原型對象身上的,而是經過jQuery.extend這個方法來擴展的。json
(function(w){ //工廠 function jQuery(selector){ return new jQuery.fn.init(selector); } //1.jQuery原型替換,2.給原型提供一個簡寫方式 jQuery.fn = jQuery.prototype = { constructor : jQuery, version : '1.0.0', toArray : function(){ return [].slice.call(this); }, each : function(fun){ return jQuery.each(this, fun); }, get : function(index){ if(index == undefined || index == null){ return this.toArray(); } if(index >=0 && index < this.length){ return this[index]; } if(index >= -(this.length - 1) && index < 0){ return this[this.length + index]; }else{ return undefined; } } }; //給jQuery的構造函數和原型對象,都添加一個extend方法,該方法用來拓展對象功能 jQuery.extend = jQuery.fn.extend = function(obj){ for(var key in obj){ this[key] = obj[key]; } }; //給jQuery添加靜態方法 jQuery.extend({ //去除首尾空格 trim : function(str){ if(!str){ return str; }else if(str.trim){ return str.trim(); } return str.replace(/^\s+|\s+$/, ''); }, //判斷是否是HTML標籤片斷 isHTML : function(str){ //null、undefined、0、NaN、false、'' if(!str){ return false; } //<p> <span> //若是字符串的第一個字母是<,最後一個字母是>,而且length >= 3,就能夠認爲是html片斷。 if(str.charAt(0) == '<' && str.charAt(str.length - 1) == '>' && str.length >= 3){ return true; } return false; }, isString : function(str){ return typeof str === 'string'; }, isNumber : function(num){ // return typeof num === 'number' && isFinite(num); //isFinite(4/0) : false return Object.prototype.toString.call(num) === '[object Number]'; }, isBool : function(arg){ // return typeof num === 'number' && isFinite(num); //isFinite(4/0) : false return Object.prototype.toString.call(arg) === '[object Boolean]'; }, isObj : function(obj){ return Object.prototype.toString.call(obj) === '[object Object]'; }, isArray : function(arr){ return Object.prototype.toString.call(arr) === '[object Array]'; }, //判斷是否是僞數組 isLikeArray : function(likeArr){ //1.具備length屬性 //2.要麼沒成員 arr.length === 0,要麼必定存在key爲arr.length - 1 的成員(沒辦法對全部的key都作判斷) //3.對象的__proto__ != Array.prototype return ('length' in likeArr) && ( likeArr.length === 0 || likeArr.length - 1 in likeArr ) && (likeArr.__proto__ != Array.prototype); }, isFunction : function(fun){ return Object.prototype.toString.call(fun) === '[object Function]'; }, //each(index, element)迭代函數 each : function(obj, fun){ if(jQuery.isFunction(fun)){ if(jQuery.isArray(obj) || jQuery.isLikeArray(obj)){ for(var i = 0; i< obj.length; i++){ fun.call(obj[i], i, obj[i]); } }else{ for(var key in obj){ fun.call(obj[key], key, obj[key]); } } } return obj; } }); //AJAX模塊 jQuery.extend({ ajax : function(jsonData){ var xhr = null; if(window.XMLHttpRequest){//標準的瀏覽器 xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //配置參數 var type = jsonData.type == 'get'?'get':'post'; var url = ''; if(jsonData.url){ url = jsonData.url; if(type == 'get'){ url += "?" + jsonData.data; } } var flag = jsonData.asyn == 'true'?'true':'false'; xhr.open(type,url,flag); //指定回調函數 xhr.onreadystatechange = function(){ if(this.readyState == 4 && this.status == 200){//請求成功 if(typeof jsonData.success == 'function'){ var d = jsonData.dataType == 'xml' ? xhr.responseXML : xhr.responseText; jsonData.success(d); } }else{//請求失敗 if(typeof jsonData.failure == 'function'){ jsonData.failure(); } } }; //發送請求 if(type == 'get'){ xhr.setRequestHeader("If-Modified-Since","0"); xhr.send(null); }else if(type == 'post'){ xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); xhr.send(jsonData.data); } } }); //獲取樣式 jQuery.extend({ getStyle : function(dom, style){ //判斷瀏覽器是否支持主流瀏覽器獲取樣式的api if(window.getComputedStyle){ return window.getComputedStyle(dom)[style]; }else{//IE8瀏覽器兼容處理 return dom.currentStyle[style]; } } }); //事件註冊模塊 jQuery.fn.extend({ on : function(type, fun){ if(jQuery.isString(type) && jQuery.isFunction(fun)){ this.each(function(i, e){ if(e.addEventListener){//主流瀏覽器支持的方式 e.addEventListener(type, fun); }else{ e.attachEvent('on' + type, fun);//IE瀏覽器支持的方式 } }); } return this; }, click : function(fun){ this.on('click', fun); return this; } }); //屬性操做模塊 jQuery.fn.extend({ attr : function(attr, val){ /* * 實現思路: * 一、判斷attr是否是字符串或者對象,不是直接return this。 * 二、若是是字符串,那麼繼續判斷arguments的length * 三、length爲1,則獲取第一個元素指定的屬性節點值返回 * 四、length>=2,則遍歷全部元素,分別給他們設置新的屬性節點值( setAttribute ) * 五、若是不是字符串(是對象),那麼遍歷這個對象,獲得全部的屬性節點值, * 而後遍歷全部的元素,把全部的屬性節點分別添加到這些元素中。 * 六、return this。 * */ // 不是字符串也不是對象,直接返回this if( !jQuery.isString( attr ) && !jQuery.isObject( attr ) ) { return this; } // 若是是字符串 if( jQuery.isString( attr ) ) { // 若是length爲1,則直接返回第一個元素的屬性節點值 if( arguments.length === 1 ) { return this.get( 0 ).getAttribute( attr ); } // 若是length爲多個(2和及2個以上) // 則遍歷全部的元素,分別設置屬性節點值 else { for( var i = 0, len = this.length; i < len; i++ ) { this[ i ].setAttribute( attr, val ); } } } // 若是是對象 // 遍歷這個對象,和全部的元素,分別添加遍歷到的屬性節點值 else { // 遍歷獲得全部的屬性節點和屬性節點值 for( var key in attr ) { // 遍歷獲得全部的元素 for( var i = 0, len = this.length; i < len; i++ ) { this[ i ].setAttribute( key, attr[ key ] ); } } } // 鏈式編程 return this; } }); //樣式操做模塊css() jQuery.fn.extend({ css : function(name, val){ if(arguments.length === 1){ //只有1個參數,而且參數是字符串,就是要讀取元素的樣式值 if(jQuery.isString(name)){ return jQuery.getStyle(this[0], name); } else if(jQuery.isObj(name)){//賦值的操做,好比傳遞的參數是{color: red, width:'400px'} for(var key in name){ this.each(function(i, e){ //注意:這裏必定不能寫成:this[i]['style'][key] = name[key]; //由於在each中,已經把fun執行時的this替換成了this[i] this['style'][key] = name[key]; }); } } } else if(arguments.length >= 2){ this.each(function(i, e){ this['style'][name] = val; }); } //鏈式編程 return this; } }); //css類操做模塊 jQuery.fn.extend({ addClass : function(clsName){ //思路: //1.先判斷dom元素是否有該class //2.若是沒有就添加,若是有就不能再添加了 this.each(function(){ if((' ' + this.className + ' ').indexOf(' ' + clsName + ' ') == -1){ this.className += ' ' + clsName; } }); return this;//鏈式編程 }, removeClass : function(clsName){ //1.若是沒傳參數,就把全部DOM對象的class屬性清空 //2.若是傳遞參數了,就遍歷全部DOM對象,把class屬性值的對應字符串,用‘ ’替換 if(arguments.length == 0){ this.each(function(){ this.className = '' ; }); }else{ this.each(function(){ this.className = jQuery.trim((' ' + this.className + ' ').replace(' ' + clsName + ' ', ' ')); }); } return this;//鏈式編程 } }); //init纔是jQuery真正的構造函數 var init = jQuery.fn.init = function(selector){ if(!selector){ return this; } //若是是字符串 if(jQuery.isString(selector)){ selector = jQuery.trim(selector); //若是是HTML標籤片斷,則建立對應的DOM, //而後添加到實例(this)身上,這裏的this是由工廠jQuery調用init構造函數new出來的對象 if(jQuery.isHTML(selector)){ /** * 因爲存在標籤嵌套<div><span></span></div>的可能,因此不能簡單的經過字符串切割,找到標籤名,去createElement. * 建立的思路: * 1.先建立一個臨時的容器div * 2.設置這個div的innerHTML爲這個selector字符串。這些標籤就成爲了div的子元素 * 3.而後遍歷這個div容器的子元素,依次添加到this身上 * 4.最後追加length屬性 */ var tempDiv = document.createElement('div'); tempDiv.innerHTML = selector; [].push.apply(this, tempDiv.childNodes); // Array.prototype.push.apply(this, tempDiv.childNodes); this.length = 1; return this; }else{ //selector是選擇器 try{ var firtChar = selector.charAt(0); var lastChars = selector.substr(1); if(firtChar == '#'){//id選擇器 var obj = document.getElementById(lastChars); if(obj == null){ return this; } [].push.call(this, obj); }else{ if(firtChar == '.'){//類選擇器 var objs = document.getElementsByClassName(lastChars); [].push.apply(this, objs); }else{//標籤選擇器 var objs = document.getElementsByTagName(selector); [].push.apply(this, objs); } } return this; }catch{ this.length = 0; return this; } } } }; //把構造函數的原型,替換爲jQuery工廠的原型 //這麼作的目的是爲了實現jQuery的插件機制,讓外界能夠經過jQuery方便的進行擴展 init.prototype = jQuery.fn; w.jQuery = w.$ = jQuery; })(window)