jQuery简单封装

370 阅读6分钟

框架封装

1.入口函数

1.0

/*
库的基本要求:
	1.独立性,不依赖任何其它第三方库
	2.隔离性,里面使用的东西与世隔绝即不会产生污染与冲突
*/
(function(global){	
    //2.
    function jQuery(selector){
        //const elements = document.querySelectorAll(selector);
        // css() 方法不能直接挂载到 elements 上,可以考虑挂载到原型对象上
        // 否则每次调用 jQuery 都会产生一个 css 方法,造成内存泄露
        //return elements;
        return new F(selector);
    }
    // 3.挂载到 HTMLCollection.prototype 原型对象,但是破坏了原有对象的结构
    // HTMLCollection是伪数组
    // 解决方案:在 jQuery 函数中内置一个类,将类似 css() 的公共方法挂载到该类的原型对象上
    function F(selector){
        this.elements = document.querySelectorAll(selector);
    }
    F.prototype.css = function(attr,value){
        for(let i = 0;i<this.elements.length;i++){
            let element = this.elements[i];
            element.style[attr]=value;
        }
    }
    
    //1.将库API挂载到全局对象上暴露API给外部使用
    global.$ = global.jQuery = jQuery;
})(window);
//HTML元素属性小驼峰命名
$(".header").css("backgroundColor", "pink")

2.0

(function(global){	
    function jQuery(selector){
        return new F(selector);
    }
  	//内置类
    function F(selector){
        this.elements = document.querySelectorAll(selector);
    }
    F.prototype = {	//方便一次性挂载多个methods
        constructor:F,
        css(attr,value){
            for(let i = 0;i<this.elements.length;i++){
                let element = this.elements[i];
                element.style[attr]=value;
            }
        }
    }
    
    global.$ = global.jQuery = jQuery;
})(window);
//每次调用都 new 了一个对象
var $1=$("div");
var $2=$("div");

3.0

//将内置类直接集成到 jQuery 函数中,将内置类 init 的实例称之为 jQuery 对象
(function(global){	
    function jQuery(selector){
        return new jQuery.prototype.init(selector);
    }
    //将 jQuery.prototype 暴露出来
    jQuery.fn = jQuery.prototype = {
        constructor:jQuery,
        init:function(selector){
//jquery为了后续DOM操作方便,将获取的DOM元素以“伪数组”的形式放在自己对象身上,可使用for循环遍历
             const elements = document.querySelectorAll(selector);
             for(let i = 0;i<elements.length;i++){
                 this[i] = elements[i];	//ele:DOM元素
             }
             this.length=elements.length;
        },
        css(attr,value){
            for(let i = 0;i<this.length;i++){
                let element = this[i];
                element.style[attr]=value;
            }
        }
    }
    //让 init 构造函数的实例也能访问到 css() 等方法
    // instance --> init.prototype --> jQuery.prototype --> css 
    jQuery.fn.init.prototype = jQuery.fn;
    
    global.$ = global.jQuery = jQuery;
})(window);

2.extend

1.0

/*
	直接挂载到 jQuery 函数上,作为静态成员
	将obj2、obj3中的属性一一遍历添加到obj对象中
    $.extend(obj,obj2,obj3)
*/
jQuery.extend = function(...args){
    //所以建议不要指定形参,通过函数内置对象arguments来进行操作
    console.log(args);
    const target = args[0];
    //进行对象拷贝,需要将第二个参数及其后面的所有参数中的属性遍历添加到第一个参数中
    for(let i = 1;i<args.length;i++){
        //每一个实参:对象
        let arg = args[i];
        //取出对象中的每一个属性
        for (let key in arg) {
            //把该属性添加到第一个参数中
            target[key] = arg[key];
        }
    }
    return target;
}

2.0

/*
	第二种 $.fn.extend 方法,用于编写jquery插件的核心方法,将功能挂载到原型中
	$.fn.extend({
        dateTimePicker(){},
        getDate(){}
    })
*/
jQuery.fn.extend = jQuery.extend = function(...args){
    //后面的拷贝过程都是一样的
    let target,source=[];
    source=[...args];
    //判断2种情况       
    //$.extend --> jQuery.extend 方法调用形式
    if(this === jQuery){
        target=args[0];
        source.splice(0,1); //删除第一个元素
    }else{	
        target = this; //$.fn.extend 即原型对象调用,往原型对象上挂载
    }
    //实现拷贝部分的逻辑:
    source.forEach(function(item,index){
        //item:就是每一个数据源对象(提供数据的对象)
        //取出item对象中的每一个属性:for...in
        Object.keys(item).forEach((key)=>{
            //key就是对象中每一个属性名
            target[key]=item[key];
        })
        //target如果本身就是一个对象字面量
        //target={ ...target,...item };
        //考虑的完美一些:如果target本身并不是一个对象字面量,上述的方式就修改了target数据类型
        Object.assign(target,item)
    });
    return target;
}

3.0

jQuery.fn.extend = jQuery.extend = function(...args){
    let target,source=[];
    source=[...args];
    //判断2种情况       
    if(this === jQuery){
        target=args[0];
        source.splice(0,1); 
    }else{	
        target = this;  
    }
    Object.assign(target,...source);
    return target;
}

4.0

//将 extend 调用分为单参数,多参数调用
/*
	单参数: extend 方法谁(jQuery、jQuery.fn)调用则往谁身上挂载参数对象上的方法
	多参数: 完成类似 Object.assign() 的功能,合并多个对象
*/
jQuery.fn.extend = jQuery.extend = function(...args){
    let target,source=[];
    source=[...args];    
    if(args.length === 1){
        target = this;	
    }else{	
        //target=args[0];	source.splice(0,1); 
        target = source.shift(0);
    }
    //实现拷贝部分的逻辑:
    Object.assign(target,...source);
    return target;
}
//添加工具方法 ---> 挂载到 jQuery 函数对象上作为 static 方法
jQuery.extend({
    each(){
        console.log('each方法');
    },
    ajax(){
        console.log('ajax方法');
    }
})
//添加DOM ---> 挂载到 jQuery 的原型对象上作为实例对象共享方法
 jQuery.fn.extend({
     attr(){
         console.log('attr方法');
     },
     on(){
         console.log('on方法');
     }
 })

3.each

1.0

// 1. $.each([1,3,5],function(index,value){})
// 2. $.each({ age:18,height:200 },function(key,value){})
jQuery.extend({
    each: function (obj, callback) {
        //obj:数组/伪数组使用 for...of ;对象 for...in
        if ((length in obj) && obj.length >= 0) {
            for (let i = 0; i < obj.length; i++) {
                callback(i, obj[i]);
            }
        } else {
            for (const key in obj) {
                if (obj.hasOwnProperty(key)) {
                    const element = obj[key];
                    callback(key, element);
                }
            }
        }

    }
})
//usage
$.each([1,3,5],function(index,value){
    console.log(index,value);
    console.log(this);//window
});
$.each({ 0:100,1:200,2:300,length:3 },function(index,value){
    console.log(index,value);
})
$.each( { name:"张三",length:-1 },function(i,v){
    console.log(i,v);
})
$.each( [{name:""},{age:""},{length:""}],function(){
    console.log(this);
} )

2.0

//改变 extend 的调用方式以及callback中的 this 
jQuery.extend({
    each: function (obj, callback) {
        //obj:数组/伪数组使用 for...of ;对象 for...in
        if ((length in obj) && obj.length > 0) {
            for (let i = 0; i < obj.length; i++) {
                callback.call(obj[i],i, obj[i]);
            }
        } else {
            for (const key in obj) {
                if (obj.hasOwnProperty(key)) {	//等价于 Object.keys(obj)
                    const element = obj[key];
                    callback.call(obj[key],key, element);
                }
            }
        }
    }
});
//使用 jQuery.extend 柯里化处理 伪数组的jQuery对象
jQuery.fn.extend({
    each:function(callback){
        // this --> instance of init --> 称为jQuery对象
        jQuery.each(this,callback);
    }
});
//
 $("div").each(function(index,element){
     element.style.color="blue";
     console.log(this)
 })

4.type

//判断类型
jQuery.extend({
    type:function(value){
        let type = Object.prototype.toString.call(value);
        return type.replace(/[\[\]\s]/g,"").replace(/object/,"").toLowerCase();
    }
});

5.css

//css移至 jQuery.fn 中并扩展
/*
	1、获取样式$("div").css("color")  只能获取到第一个div的颜色
    2、设置样式
       $("div").css("color","red") 设置每一个div的字体颜色
       $("div").css({ color:"red","backgroundColor","blue" })
*/
css(...args) {
    if (args.length === 1) {    //attrString or Obj
        const data = args[0], firstDOM = this[0];
        if ($.type(data) === "string") {
            // return firstDOM.style[data]; 只能获取行内样式
            let domStyleObj = window.getComputedStyle(firstDOM, null);
            return domStyleObj[data];
        } else {
            this.each(function () {
                let self = this;
                jQuery.each(data, function (key, value) {
                    self.style[key] = value;
                });
            });
        }
    } else {  // attr, value
        let [attr, value] = args;
        this.each(function (index, element) {
            // element.style[attr] = value;
            this.style[attr] = value;
        });
    }
}

6.添加链式调用

/*
	分析:什么功能可以链式,什么不能链式
    原理:如果该功能必须要有返回值,那么该功能就不能实现链式
             获取样式 -->
             获取属性 
             获取内容 --> $("div").html()
        如果该功能有没有返回值无所谓,那么该功能就可以实现链式调用
             设置样式 --> 通过页面就可以验证有没有成功,所以返回值要不要无所谓
             设置内容 --> $("div").html("<span>abc</span>")
*/
jQuery.fn.extend({
    each(callback){
        //this:jquery对象
        jQuery.each(this,callback)
        return this;	//链式调用
    }
});
jQuery.fn.extend({
    css(...args) {
        if (args.length === 1) {    //获取单个属性值
            const data = args[0], firstDOM = this[0];
            if ($.type(data) === "string") {
                let domStyleObj = window.getComputedStyle(firstDOM, null);
                return domStyleObj[data];
            } else {	//修改多个属性
                let self = this;
                jQuery.each(data, function (key, value) {
                    self.css(key,value);
                });
                return self;
            }
        } else {  //修改单个属性 attr, value
            let [attr, value] = args;
            return this.each(function (index, element) {
                this.style[attr] = value;
            });
        }
    }
});
//usage
$("div").css({
    color: "green",fontSize: "20px"
}).css("border","1px solid ");

7.show & hide

//作为 init 内置类实例的原型方法
jQuery.fn.extend({
    hide:function(){
        this.css("display","none");
        return this;
    },
    show:function(){
        this.css("display","block");
        return this;
	}
});

8.toggle

//修改 init 方法,让其能将 DOM 元素包装成 jQuery 对象
jQuery.fn = jQuery.prototype = {
    constructor:jQuery,
    //init是一个构造函数-->构造函数内部的this指向init的实例
    init:function(selector){
        if(jQuery.type(selector)==="string"){
            const elements = document.querySelectorAll(selector)
            for(let i = 0;i<elements.length;i++){
                this[i] = elements[i];
            }
            this.length=elements.length;
        }else if( selector.nodeType ){	//DOM元素
            this[0] = selector;
            this.length = 1;
        }
    }
}

jQuery.fn.extend({
	toggle(){
        this.each(function(){
            // this --> 原生DOM对象 --> 无法使用 jQuery Api
            //问题:jquery(this)都会产生一个新的jquery对象
            //而每一次产生一个新的jquery对象都会开辟一块新的内存,
            //而这里的dom元素是唯一的,所以导致了一些不必要的内存浪费
            // jQuery(this).css("display")==="none"?
            // jQuery(this).show():
            // jQuery(this).hide()
            //解决方案:
            // let $this=jQuery(this);
            // $this.css("display")==="none"?
            // $this.show():
            // $this.hide();
            //简化的解决方案2:
            let $this=jQuery(this);
            $this[$this.css("display")==="none"?"show":"hide"]();
        })
    }
})

分拆jQuery

jQuery.core.js

(function(global){
    function jQuery(selector){
        return new jQuery.fn.init(selector);
    }
    jQuery.fn = jQuery.prototype = {
        constructor:jQuery,
        init:function(selector){
            if(jQuery.type(selector)==="string"){   //选择器
                const elements = document.querySelectorAll(selector)
                for(let i = 0;i<elements.length;i++){
                    this[i] = elements[i];
                }
                this.length=elements.length;
            }else if( selector.nodeType ){  //DOM元素
                this[0] = selector;
                this.length = 1;
            }
        }
    }
    jQuery.fn.init.prototype = jQuery.fn;
    
    //$.extend
    //  1、如果有一个参数,把参数对象里面的属性依次拷贝给$
    //      $.extend({ name:"abc",age:18 })
    //    -->$.name="abc"
    //    -->$.age=18
    //  2、如果有多个参数,把第二个参数及其后面的所有参数中的属性依次遍历给第一个参数
    //      var p={}
    //      $.extend(p,{a:10},{b:20},{c:30})
    //              p.a=10;
    //              p.b=20;
    //              p.c=30
    
    //$.fn.extend
    //  1、如果有一个参数,把参数对象中的属性依次遍历给$.fn
    //      $.fn.extend({ css:function(){},on:function(){} })
    //          $.fn.css=function(){}
    //          $.fn.on=function(){}
    //  2、如果有多个参数,功能等价于$.extend的第二个功能
    //      $.fn.extend(p,{a:10},{b:20},{c:30})
    //      $.extend(p,{a:10},{b:20},{c:30})
    //      -->p.a=10 p.b=20 p.c=30;

    jQuery.fn.extend=jQuery.extend=function(...args){
        //接收数据的对象
        let target;
        let sources=[];
        //参数个数为1:
        if(args.length===1){
            target=this;
            sources.push(args[0]);
        }else{
            //参数个数>1:
            target=args[0];
            sources.push(...args);
            sources.splice(0,1);
        }
        //完成拷贝的逻辑
        sources.forEach(function(source){
            //获取对象中的每一个属性:
            Object.keys(source).forEach(function(key){
                target[key] = source[key];
            })
        });
        //告知用户拷贝的结果
        return target;
    }
    global.$ = global.jQuery = jQuery;
})(window)

jQuery.style.js

//样式操作部分
jQuery.fn.extend({
    //1、获取样式$("div").css("color")  只能获取到第一个div的颜色
    //2、设置样式
    //      $("div").css("color","red") 设置每一个div的字体颜色
    //      $("div").css({ color:"red","backgroundColor","blue" })
    css(...args){
        var arg1=args[0],
            arg2=args[1];
        //参数个数:1
        if(args.length === 1){
            if(jQuery.type(arg1)==="string"){
                //a、获取样式:只能获取第一个元素的样式
                let firstDom = this[0];
                //错误写法
                // return firstDom.style[arg1]; //只能获取行内样式
                //正确的写法
                let domStyleObj = window.getComputedStyle(firstDom,null)
                return domStyleObj[arg1];
            }else{
                //b、设置多个样式  
                //arg1:{ color:"red",fontSize:"20px" }
                var _that=this;
                //遍历出所有要添加的样式
                jQuery.each(arg1,function(key,value){

                    //遍历每一个DOM元素,添加指定的样式
                    _that.css(key,value);
                });
				return _that;
            }
        }else{
            //参数个数:2  设置单个样式
            //第一步:遍历每一个DOM
            //第二步:给DOM添加样式
            //this:表示一个jquery对象
            // this.each(function(index,dom){
            //     //this:表示一个DOM元素  ===   dom
            //     this.style[arg1] = arg2;
            // });
            // return this;
            //等价于:
            //$("div").css()        
            //this->$("div")
            //this: { 0:div,1:div,2:div,length:3 }
            return this.each(function(index,dom){
                //this:表示一个DOM元素  ===   dom
                this.style[arg1] = arg2;
            });
        }
        
    },
    show(){
        //不会涉及动画
        //功能:让所有的元素显示出来
        this.css("display","block");
        return this;
    },
    hide(){
        this.css("display","none");
        return this;
    },
    toggle(){
        //判断每一个元素,如果隐藏就显示,如果显示就隐藏
        this.each(function(){
            //问题:jquery(this)都会产生一个新的jquery对象
            //而每一次产生一个新的jquery对象都会开辟一块新的内存,
            //而这里的dom元素是唯一的,所以导致了一些不必要的内存浪费

            // jQuery(this).css("display")==="none"?
            //     jQuery(this).show():
            //     jQuery(this).hide()

            //解决方案:
            // let $this=jQuery(this);
            // $this.css("display")==="none"?
            //     $this.show():
            //     $this.hide();

            //解决方案2:
            let $this=jQuery(this);
            $this[$this.css("display")==="none"?"show":"hide"]();

        })
    }
});

jQuery.util.js

jQuery.extend({
    //可以遍历数组和对象
    each(obj,callback){
        //有2种情况,数组使用for循环,对象使用for...in循环
        //不仅仅可以遍历数组,也可以遍历伪数组
        //{ length:0 }
        //{ 0:100,length:1 }
        //{ 0:"a",1:"b",2:"c",length:3 }
        //在这里,由于存在数组、伪数组2种情况,只能使用一种约定俗成的方式来通过他们的特征来进行判断:length属性,并且值>=0
        if( (length in obj) && obj.length>=0 ){
            for(let i =0;i<obj.length;i++){
                callback.call(obj[i],i,obj[i])
                //callback.apply(obj[i],[i,obj[i]])
                //没有必要使用bind,bind的实现相对繁琐
                // callback.bind(obj[i])(i,obj[i])
            }
        }else{
            for(let i in obj){
                callback.call(obj[i],i,obj[i])
            }
        }
    },
    type(data){
        //判断data的数据类型
        //-->Object.prototype.toString.call(1)
        //  -->"[object Number]"
        var type=Object.prototype.toString.call(data);
        return type
                .replace("[object ","")
                .replace("]","")
                .toLowerCase();
    }
})

jQuery.fn.extend({
    each(callback){
        //this:jquery对象
        jQuery.each(this,callback)
        return this;
    }
});

jQuery.event.js

(function(){
    //将会保存曾经绑定过的所有的事件处理函数
    //以DOM元素为区分,
    const events=[
        //{ ele:div1,type:"click",callback:function(){} },
        //{ ele:div1,type:"click",callback:function(){} },
        //{ ele:div1,type:"keydown",callback:functioN(){} },
        //{ ele:div3,type:"click",callback:function(){} }
    ];

    jQuery.fn.extend({
        //$("div").on("click",function(){})
        on(type,callback){
            //给当前jquery对象中的每一个DOM元素绑定事件
            this.each(function(index,element){
                element.addEventListener(type,callback);
                events.push({ ele:element,type,callback })
            });
            //实现链式编程
            return this;
        },
        //解绑绑定:$("div").off("click"):表示解除当前元素的所有的单击事件
        off(type){
            this.each(function(index,element){
                //遇到一个问题:并不能得到之前绑定事件的回调函数的地址
                //-->解决方案:必须在当前绑定事件的时候,把事件回调函数的内存地址保存起来
                // element.removeEventListener(type,)

                //找到该元素曾经绑定过type类型的事件
                var evts = events.filter(function(evtObj){
                    //是否是该元素绑定的该类型的事件
                    var isCurrent=evtObj.ele === element && evtObj.type === type;
                    return isCurrent;
                });
                //进行事件解绑操作
                evts.forEach(function(evt){
                    var { callback } = evt;
                    element.removeEventListener(type,callback);
                })
            })
        }
    })
})()
//usage
$("div").css({
    color: "green",fontSize: "20px"
}).on("click",function(){
    console.log("hello there");
}).on("click",function(){
    console.log("222");
});

事件基础

 var divHeader=document.getElementById("divHeader");
 divHeader.onclick=function(){
     console.log('abc');
 }
<div onclick="console.log(100)">abc</div>
<div onclick="f1()"></div>
/*
以上3种都是属于DOM0时代的事件处理方法
DOM0:是指DOM标准诞生之前浏览器提供的对DOM的相关操作
存在问题:
    事件只能绑定一次,如果多次绑定,会导致覆盖
    这种问题在DOM1中并没有得到解决
    在DOM0事件到DOM2事件之间IE又出来捣乱了(IE6/7/8)
         -->dom.attachEvent("onclick",function(){
             //要获取事件对象:window.event
             //阻止冒泡:window.event.cancelBubble=true;
             //阻止默认行为:window.event.returnValue=false
    })
         对同一个元素绑定了多个同类型事件,事件触发顺序会按照绑定顺序【倒序】触发
           div.attachEvent("onclick",function(){ console.log(1)  })
           div.attachEvent("onclick",function(){ console.log(2)  })
           div.attachEvent("onclick",function(){ console.log(3)  })
         触发顺序: 3-->2-->1
       -->事件的解绑:dom.detachEvent("onclick",function(){})

    一直到DOM2才得到解决  (从IE11开始、Edge也支持)
         dom.addEventListener("click",function(e){
                    1、获取事件对象:e
                    2、阻止冒泡:e.stopPropagation()
                    3、阻止默认行为:e.preventDefault();
                    4、事件末尾,通过return false同时实现阻止冒泡和阻止默认行为
    })
    对同一个元素绑定了多个同类型事件,事件触发顺序会按照绑定顺序【顺序】触发
    事件的解绑:dom.removeEventListener("click",function(){})

    DOM2中提供的addEventListener这个方式支持事件流的操作
    事件流分为3个阶段:捕获-->目标元素阶段-->冒泡
    div.addEventListener("click",回调函数,false)
    其中第三个参数:false表示在冒泡阶段触发
    true表示在捕获阶段触发
*/

//事件解绑
var div=document.querySelector("div");
//希望让事件只触发一次
div.addEventListener("click",function f1(){
    // f1可以在函数体内部随意访问
    // -->f1指向当前函数本身  === arguments.callee
    alert("1");
    // a、对事件解绑
    // var _this=arguments.callee; //当前函数本身
    // div.removeEventListener("click",_this);
    //b、由于ES5严格模式的诞生,arguments.callee被禁用了,
    div.removeEventListener("click",f1);
});
//外界访问不了f1变量
console.log(typeof f1);