jquery是如何封裝的

這篇文章是分析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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章