zoukankan      html  css  js  c++  java
  • 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, '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

  • 相关阅读:
    [APIO2018]铁人两项 --- 圆方树
    SPOJ1811 && SPOJ1812
    [BZOJ4627][BeiJing2016]回转寿司(线段树)
    NOIP2018提高组题解
    [BZOJ4340][BJOI2015]隐身术(后缀数组)
    [BZOJ4338][BJOI2015]糖果(扩展Lucas)
    [BZOJ4336][BJOI2015]骑士的旅行(树链剖分+线段树)
    [BZOJ4419][SHOI2013]发微博
    [BZOJ2878][NOI2012]迷失游乐园(环套树DP+概率)
    [BZOJ1791][IOI2008]Island岛屿(环套树DP)
  • 原文地址:https://www.cnblogs.com/JAYIT/p/13022276.html
Copyright © 2011-2022 走看看