這篇文章是分析jQuery是如何封裝的。這裏把我自己模擬jQuery封裝的一個類庫拿出來分享。
一、首先做一點說明
1.這篇文章可以看做是我之前的一篇博文 淺析jQuery基本原理($實現原理)的續篇
2.個人認爲jQuery 與其他庫相比,它有3個最大的特點,其一是獨有的jQuery對象,其二是隱式迭代,其三是鏈式編程。
3.所以我所封裝的庫,重點就在於描述jQuery的這3個特徵是如何實現的,而不是真的要做一個完美的庫,我們要說的是思想,是方法,而不是講項目。
4.牛頓說他之所以看得遠,是因爲站在了巨人的肩膀上。而jQuery就是我們學習前端的一個巨人,我們應該拿它的源碼來學習下。
二、直接上代碼
代碼中有一些必要的說明。不過不能說足夠,主要是有點多,我懶得再做說明了。這裏簡單的提一點,jQuery.extend是一個核心模塊,這個模塊是用來拓展jQuery其他api的。換句話說,其他的api都不是直接就寫到jQuery對象或者其原型對象身上的,而是通過jQuery.extend這個方法來擴展的。
(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){ /* * 實現思路: * 1、判斷attr是不是字符串或者對象,不是直接return this。 * 2、如果是字符串,那麼繼續判斷arguments的length * 3、length爲1,則獲取第一個元素指定的屬性節點值返回 * 4、length>=2,則遍歷所有元素,分別給他們設置新的屬性節點值( setAttribute ) * 5、如果不是字符串(是對象),那麼遍歷這個對象,得到所有的屬性節點值, * 然後遍歷所有的元素,把所有的屬性節點分別添加到這些元素中。 * 6、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)
轉載於https://www.cnblogs.com/ldq678/p/9666914.html