• 仿照jquery封装一个自己的js库


    所谓造轮子的好处就是复习知识点,加深对原版jquery的理解。
    本文系笔者学习jquery的笔记,记述一个名为"dQuery"的初级版和缩水版jquery库的实现。主要涉及知识点包括面向对象,jquery,绑定,脚本化css等。


    一. jquery的美元符意味什么?

    先思考alert(typeof $)的结果中的$,它是一个对象,也是一个函数。
    所以美元符字面上是jQuery,其实就是一个jq对象,里面可以接受函数,字符串(#xxx,.xxx,xxx...),还有一种是对象(比如this)。

    dQuery是基于面向对象实现的,所以需要先写构造函数。为了方便遍历和其它方法操作,所有内容返回到一个数组中。这个数组依附于dQuery对象存在,之后将为这个dquery对象属性添加方法。

    function dQuery(vArg){//参数是变体变量
        this.elements=[];//选择器选择的元素扔到这个数组中
    }

    因为新的对象可以接受三种类型的参数(字符串,对象,函数)。作为一个变体变量,根据匈牙利命名法可以用vXxx命名,同时需要分类讨论变量的情况:

    1.当参数为函数时,执行函数——window.onload.——不好。

    jquery写作时,经常允许多个$(function()),但是写window.onload会导致最终只执行最后一个。这时需要给函数绑定window.onload事件。
    绑定事件的函数如下:

    function myAddEvent(obj,sEv,fn){
        if(obj.attachEvent){
            obj.attachEvent('on'+sEv,fn);
        }else{
            obj.addEventListener(sEv,fn,false);
        }
    }

    所以,当为id选择器时,查找:document.getElementById(vArg.substring(1));并把它加到this.elements中去。

    2.当参数为字符串时,执行选择器操作.

    这里需要对id选择器,类选择器,元素选择器进行判断。
    注意:把选择器结果丢到一个数组(dQuery.elements)中去。方便遍历事件。
    用charAt进行判断参数首位:(#,.或是其它)

    (1)首位为#时:

    执行document.getElementById,同时把这个字符串稍作处理,用substring(1)方法把首个字符给去掉。

    (2)首位为.

    选取class类。可以设计一个getByClass函数

    function getByClass(oParent,sClass){
        var aEle=oParent.getElementsByTagName('*');//选择父元素的所有元素
        var aResult=[];
        var re=new RegExp('\b'+sClass+'\b','i');//正则边界
        var i=0;
        for(i=0;i<aEle.length;i++){
            if(re.test(aEle[i].className)){
                aResult.push(aEle[i]);
            }
        }
        return aResult;
    }

    在本实例中,oParent父级对象实际上就是document,另一方面,因为getByClass返回的对象实际是一个数组,所以:

    this.elements=getByClass(document,vArg.substring(1));
    (3)参数为对象时,返回一个对象本身

    把这个对象push到

    (4)绑定基本的事件——比如点击事件

    在接下来就是绑定事件。比如click(function(){}),它是一个回调函数。需要用到原型(prototype)方法。

    //对选择器函数绑定click事件
    dQuery.prototype.click=function(fn){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            myAddEvent(this.elements[i],'click',fn);
        }
    }
    (5)简写设置

    到目前为止,在调用这个dQuery时,每次都需要这样写:

    new dQuery(function(){
        new dQuery('input').click(function(){
            alert('a');
        })
    });

    每次都要使用new,不用就会出问题。
    如果我想使用类似jquery的简写方式,使用$d作为简写,可以通过一个函数来定义:

    function $d(vArg){
        return  new dQuery(vArg);
    }

    所以当前的最终代码为:

    //可重复调用的加载函数
    function myAddEvent(obj,sEv,fn){
        if(obj.attachEvent){
            obj.attachEvent('on'+sEv,fn);
        }else{
            obj.addEventListener(sEv,fn,false);
        }
    }
    
    //class选择器调用函数
    function getByClass(oParent,sClass){
        var aEle=oParent.getElementsByTagName('*');//选择父元素的所有元素
        var aResult=[];
        var re=new RegExp('\b'+sClass+'\b','i');//正则边界
        var i=0;
        for(i=0;i<aEle.length;i++){
            if(re.test(aEle[i].className)){
                aResult.push(aEle[i]);
            }
        }
        return aResult;
    }
    
    //定义dQuery对象
    function dQuery(vArg){//参数是变体变量
        this.elements=[];//选择器选择的元素扔到这个数组中
        switch(typeof vArg){
            //如果参数是函数
            case 'function':
                myAddEvent(window,'load',vArg);
                break;
            //如果参数是字符串
            case 'string':
                switch(vArg.charAt(0)){
                    case '#'://id选择器参数应该为#号之后的字符段
                        var obj=document.getElementById(vArg.substring(1));
                        this.elements.push(obj);
                    break;
    
                    case '.'://class
                        this.elements=getByClass(document,vArg.substring(1));
                        break;
    
                    default://标签
                        this.elements=document.getElementsByTagName(vArg);
                }
                break;
            //如果参数是对象。
            case 'object':
                this.elements.push(vArg);
    
        }
    }
    
    //对选择器函数绑定click事件
    dQuery.prototype.click=function(fn){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            myAddEvent(this.elements[i],'click',fn);
        }
    }
    
    function $d(vArg){
        return  new dQuery(vArg);
    }
    (6)小结
    • 通过element属性储存被选中的元素
    • 判断参数类型,以此做出不同的判断
    • 对于参数是函数的情况,需要进行绑定window.onload。
    • 对象则直接插入

    二. 添加事件

    1. 之前已经用原型方法的加上了click事件,现在研究下show/hide的实现:

    从最简单的display——none/block结果开始写起:

    //对选择器函数绑定show/hide事件
    dQuery.prototype.show=function(){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            this.elements[i].style.display='block';
        }
    }
    
    dQuery.prototype.hide=function(){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            this.elements[i].style.display='none';
        }
    };

    实现功能已经没有问题了。

    2.hover的实现:

    (1)hover有两个参数,第一个是鼠标移入函数,第二个是鼠标移出函数。实际上跟click事件一样:
    dQuery.prototype.hover=function(fnover,fnout){
        var i=0;
        for(i=0;i<this.elements.length;i++){
            //给这个对象一次性绑定两个事件
            myAddEvent(this.elements[i],'mouseover',fnover);
            myAddEvent(this.elements[i],'mouseout',fnout);
        }
    };

    就调用一般的alert方法等已经没有问题了,接下来要用类似jquery中css的写法定义它的样式

    (2)css有两种调用:设置样式,只有一个参数时获取样式。总之参数不定(1或2个,attr、value)。只有两个参数时很简单。只需要做个循环,把value值赋给相应的样式属性就可以了:
    this.elements[i].style[attr]=value;

    当只有一个参数attr时,获取的样式可能不是唯一的。这时可以参考jquery的css方法定义:获取的是第一个元素的样式——如果你用xxx.style.样式的方法——获取的仅仅是行间样式,那这就麻烦了。需要再引入一个兼容性良好的样式获取函数getStyle。

    function getStyle(obj,attr){
        //元素,样式
        if(obj.currentStyle){//兼容ie9及以下浏览器
            return obj.currentStyle[attr];
        }else{
            return getComputedStyle(obj,false)[attr];
        }
    }
    //css方法
    dQuery.prototype.css=function(attr,value){
        if(arguments.length==2){//当参数个数为2时,使用设置css的方法
            var i=0;
            for(i=0;i<this.elements.length;i++){
                this.elements[i].style[attr]=value;
                
            }
        }else{//只有一个参数时获取样式
            return getStyle(this.elements[0],attr);
        }
    };

    实验证明,传入background无效,但是对具体的background-color无效。

    (3)改进:this

    虽然说当dQuery对象的参数为一个对象时,返回的就是对象本身。但是有时候在使用this的时候(ie),this指向了其它的对象(window对象)。
    不能用this的情形——行间,定时器,绑定。js库中,实际上是绑定处理的。在此需要了解call方法。因此需要对myAddEvent函数进行修改。


    call方法
    应该知道,jquery中充满了各种简写。比如show,全称为show.call。比如下面这个例子:

    function show(){
        alert(this);
    }
    show();
    show.call();

    这个show函数的弹出结果为:window对象。show.call()弹出结果也是window对象,但call可以加参数,加了参数之后,this返回的值为参数——show.call('abc');弹出结果为abc。
    进一步,给a,b传入两个参数:

    function show(a,b){
        alert('this是:'+this+',a是:'+a+',b是:'+b);
    }
    show(12,5);
    show.call(12,5);

    调用show函数的第一个弹出结果是:

    调用show.call(12,5)弹出:

    这说明call方法中的第一个参数默认指向this。比如要调用可以写成show('abc',12,5)
    与call类似,apply也类似——
    apply('abc',[12,5])的结果和show('abc',12,5)完全一样。


    知道了call方法,回到myAddEvent函数的修改上来,应该让fn返回的主题回到obj——

    function myAddEvent(obj,sEv,fn){
        if(obj.attachEvent){
            obj.attachEvent('on'+sEv,function(){
                fn.call(obj);//兼容ie
            });
        }else{
            obj.addEventListener(sEv,fn,false);
        }
    }

    这样一来,就可以使用$d(this)了。

    3.toggle方法

    toggle方法是旧jquery中很有用的点击处理方法。可以传多个事件处理函数。每一次触发,就按顺序执行。调用形式如下:

    $d(function(){
        $d('#btn1').click(function(){
            $d('#div1').toggle(function fn1(){
    
            },function fn2(){
    
            },function fn3(){
                
            }...);
        });
    
    });

    怎么模拟这个函数呢?——

    (1)需要一个计数器,根据计数的结果选择执行哪个函数。

    toggle可以传任意个参数。所以参数干脆不加了。


    在计数器的实现过程中会出现问题。

    window.onload=function (){
        var aBtn=getElementsByTagName('button');
        var i=0;
        var count=0;//计数器
        
        for(i=0;i<aBtn.length;i++){
            aBtn[i].onclick=function(){
                alert(count++);
            }
        }
    }

    如果我有多个按钮需要触发toggle内的事件,但是按照dQuery先前的方法,必然导致所有按钮公用一套计数器。这样一来效果就全乱了。怎么解决呢?
    最好的办法是,把计数过程存为一个函数,

    window.onload=function (){
        var aBtn=getElementsByTagName('button');
        var i=0;
        
        function addClick(obj){
            var count=0;//关键步骤:计数器塞进函数里了。
            alert(count++);
        }
        
        for(i=0;i<aBtn.length;i++){
            
            aBtn[i].onclick=function(){
                addClick(aBtn[i]);
            }
        }
    }

    或者(对阅读不友好):

    window.onload=function (){
        var aBtn=getElementsByTagName('button');
        var i=0;
        
        for(i=0;i<aBtn.length;i++){
            (function(obj){
                var count=0;//关键步骤:计数器塞进函数里了。
                alert(count++);
            })(aBtn[i]);
            }
        }
    }

    分别计数就这么实现了。调用函数几次,就会产生多少个局部变量。也可以使用xxx.index这样的方法。


    现在要在toggle里面调用私有的计数器,思路也是把计数器放到一个声明的函数里边。

    dQuery.prototype.toggle=function(){
        //私有计数器,计数器会被一组对象所享用。
        function addToggle(obj){
            var count=0;
            myAddEvent(obj,'click',function(){
                alert(count++);
            })
        }
    
        var i=0;
        for(i=0;i<this.elements.length;i++){
            addToggle(this.elements[i]);
        }
    }

    接下来是使用arguments来做,用以替换掉addToggle里边的事件处理:让计数器递增的同时,选取它对参数长度取模的索引的参数,。并把本体call为参数obj(有点拗口)

    myAddEvent(obj,'click',function(){
                arguments[count++%arguments.length].call(obj);
    }

    结果出错。主要是arguments乱了,实际执行情况是,arguments是函数myAddEvent第三个参数(函数)的参数。因此,需要提前把toggle方法的arguments传入一个数组。
    完整toggle的的代码是

    //toggle方法:
    dQuery.prototype.toggle=function(){
        var _arguments=arguments;//把toggle的arguments存起来,以便在其它函数中可以调用。
        
        //私有计数器,计数器会被一组对象所享用。
        function addToggle(obj){
            var count=0;
            myAddEvent(obj,'click',function(){
                _arguments[count++%_arguments.length].call(obj);
            })
        }
    
        var i=0;
        for(i=0;i<this.elements.length;i++){
            addToggle(this.elements[i]);
        }
    }

    接下来可以做一个简单的示例验证一下效果:
    html

        <input type="button" id="btn1" value="显示隐藏">
        <div id="div1" style="100px;height:100px;background:red;"></div>

    javascript:

    $d(function(){
        $d('input').toggle(function(){
            $d('#div1').hide();
        },function(){
            $d('#div1').show();
        })
    });

    效果如下

    三. attr方法

    attr管样式,css管属性。所以方法设置方面没有太大区别。

    //attr方法和css方法类似。
    dQuery.prototype.attr=function(attr value){
        if(arguments.length==2){//设置属性
            var i=0
            for(i=0;i<this.elements.length;i++){
                this.elements[i][attr]=value;
            }
        }else{//获取属性
            return this.elements[0][attr];
        }
    }

    没有问题。

    四.获取与查找(eq,find,index)

    1.eq方法跟数组索引差不多

    在jquery中,$('.class1').eq(1)表示选取选择器的第2个对象。现在模拟jquery的取法。
    在dQuery中,很自然想到用return this.elements[n];这样的代码选取。问题在于:返回的是一个原生的js对象,因此无法调用dQuery方法。因此需要把this.elements[n]这个原生对象转化为dQuery对象。所以——

    //eq选择器
    dQuery.prototype.eq=function(n){
        return new dQuery(this.elements[n]);
    }

    这样保证了返回出来的对象是可链的。

    2.find方法

    find方法在jquery中是用于筛选。比如$('ul').find('li')表示在所有ul标签下选择li,而ol标记下的li不会被选择。此方法有一个父级,find里面,可能有class类,可能有div类,不可能有id类(为什么不直接用id选择器呢?所以不予考虑了)。
    因为返回的是一个较为复杂的对象——它是依赖于dQuery对象的一个数组(aResult)。因此具体做法和定义dQuery的object类似。需要分类讨论:

    //find选择器
    dQuery.prototype.find=function(str){
        var i=0;
        var aResult=[];//存放临时数据
    
        for(i=0;i<this.elements.length;i++){
            switch(str.charAt(0)){
                case '.'://class类
                    var aEle=getByClass(this.elements[i],str.substring(1));
                aResult.concat(aEle);//桥接到aResult内。
                break;
    
                default://其它标签名(TagName)
                    var aEle=this.elements[i].getElementsByTagName(str);
    
                    aResult.concat(aEle);
            }
        }
        var newdQuery=new dQuery();
        newdQuery.elements=aResult;
        return newdQuery;//保持可链。
    }

    这段代码有很大问题,aResult本是一个数组,但是this.elements[i].getElementsByTagName(str)是一个html collection,是一个长得很像数组的集合。因此concat方法不适用。——concat不能用,但是push可以用。为了达到桥接的效果,可以人工定义一个小函数来实现。所以全部带代码是

    //find选择器
    //定义一个小函数,两个数组(元素集合),把两个类数组(html元素集合)合并在一块。
    function appendArr(arr1, arr2){
        var i=0;
    
        for(i=0;i<arr2.length;i++){
            arr1.push(arr2[i]);
        }
    }
    
    dQuery.prototype.find=function(str){
        var i=0;
        var aResult=[];//存放临时数据
    
        for(i=0;i<this.elements.length;i++){
            switch(str.charAt(0)){
    
                case '.'://class类
                    var aEle=getByClass(this.elements[i],str.substring(1));
                aResult.concat(aEle);//桥接到aResult内。但是
                break;
    
                default://其它标签名(TagName)
                    var aEle=this.elements[i].getElementsByTagName(str);
                    appendArr(aResult,aEle);
            }
        }
        var newdQuery=new dQuery();
        newdQuery.elements=aResult;
        return newdQuery;//保持可链。
    }

    3.index方法

    jquery的原意是——获取一组同辈元素内,一组元素的索引值。
    比如

    <span></span>
    <div id="div1"></div>
    <div></div>
    <div></div>
    $(funciton(){
        $('div').click(function(){
            alert($(this).index());
        })
    })

    在上面的过程中,点击#div1,弹出的结果为1,而不是0(把span算进索引0了)。简单点说就是:如何求某个元素在同级中的位置。
    下面实现这段代码。
    首先,使用index方法的必然是一个单独的对象,而不能是一个集合或数组。所以,必须指定this.elements[0]
    一个人,比如说王五必然是他爹的儿子,要找到他的兄弟,只能通过他爹来找。接下来可以有头到尾遍历判断,假设老王的第五个儿子名字是王五,那么就可以判断,王五的索引值为4.同理,js用parentNode和child进行判断。

    //获取索引值函数
    function getIndex(obj){
        var aBrother=obj.parentNode.children;
        var i=0;
        
        for(i=0;i<aBrother.length;i++){
            if(aBrother[i]==obj){
                return i;
            }
        }
    }

    接下来要做的就是把这个封装好的函数加到原型函数内:

    dQuery.prototype.index=function(){
        return getIndex(this.elements[0]);
    }

    那么,这个方法就算完成了。为了说明这个库是实用的,接下来以一个案例说明。



    【应用案例】选项卡的实现

    html骨架

      <div id="tab">
          <ul class="list_group">
              <li><a class="active" href="javascript:;">1</a></li>
              <li><a href="javascript:;">2</a></li>
              <li><a href="javascript:;">3</a></li>
              <li><a href="javascript:;">4</a></li>
          </ul>
          <div class="box">
              <div class="content">
                  <img alt="1" src="images/1.jpg">
              </div>
              <div class="content">
                  <img alt="2" src="images/2.jpg">
              </div>
              <div class="content">
                  <img alt="3" src="images/3.jpg">
              </div>
              <div class="content">
                  <img alt="4" src="images/4.jpg">
              </div>
          </div>
      </div>

    css

    *{
        margin:0;
        padding: 0;
    }
    ul{
        list-style: none;
    }
    a{text-decoration: none;}
    
    #tab{
         400px;
        margin:100px auto;
    }
    .list_group li{
        float: left;
    }
    .list_group li a{
        display: block;
         40px;
        line-height: 30px;
        text-align: center;
    }
    .box{
        clear: both;
    }
    .content{
        display: none;
    }
    .content img{
        border: 1px solid black;
         400px;height: 300px;
    }

    在引用了dQuery的情况下,输入下列js代码:

    $d(function(){
        $d('li').click(function(){
            var index=$d(this).index();
    
            $d('.content').hide();
            $d('.content').eq(index).show();
            $d('li').css('background-color','white');
            $d(this).css('background-color','#ccc');
    
        })
    })


    就成功实现了一个丑陋的选项卡。因为不是完全可链的。还有addClass等功能还有待进一步完善。


    附录

    dQuery代码

    //可重复调用的加载函数
    function myAddEvent(obj,sEv,fn){
        if(obj.attachEvent){
            obj.attachEvent('on'+sEv,function(){
                fn.call(obj);//兼容ie
            });
        }else{
            obj.addEventListener(sEv,fn,false);
        }
    }
    
    //class选择器调用函数
    function getByClass(oParent,sClass){
        var aEle=oParent.getElementsByTagName('*');//选择父元素的所有元素
        var aResult=[];
        var re=new RegExp('\b'+sClass+'\b','i');//正则边界
        var i=0;
        for(i=0;i<aEle.length;i++){
            if(re.test(aEle[i].className)){
                aResult.push(aEle[i]);
            }
        }
        return aResult;
    }
    
    //获取计算后的样式
    function getStyle(obj,attr){
        //元素,样式
        if(obj.currentStyle){//兼容ie9及以下
            return obj.currentStyle[attr];
        }else{
            return getComputedStyle(obj,false)[attr];
        }
    }
    
    //定义dQuery对象
    function dQuery(vArg){//参数是变体变量
        this.elements=[];//选择器选择的元素扔到这个数组中
        switch(typeof vArg){
            //如果参数是函数
            case 'function':
                myAddEvent(window,'load',vArg);
                break;
            //如果参数是字符串
            case 'string':
                switch(vArg.charAt(0)){
                    case '#'://id选择器参数应该为#号之后的字符段
                        var obj=document.getElementById(vArg.substring(1));
                        this.elements.push(obj);
                    break;
    
                    case '.'://class
                        this.elements=getByClass(document,vArg.substring(1));
                        break;
    
                    default://标签
                        this.elements=document.getElementsByTagName(vArg);
                }
                break;
            //如果参数是对象。
            case 'object':
                this.elements.push(vArg);
                
        }
    }
    
    //定义简写
    function $d(vArg){
        return  new dQuery(vArg);
    }
    
    
    //对选择器函数绑定click事件
    dQuery.prototype.click=function(fn){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            myAddEvent(this.elements[i],'click',fn);
        }
    }
    
    //对选择器函数绑定show/hide事件
    dQuery.prototype.show=function(){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            this.elements[i].style.display='block';
        }
    }
    
    dQuery.prototype.hide=function(){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            this.elements[i].style.display='none';
        }
    };
    
    //hover方法
    dQuery.prototype.hover=function(fnover,fnout){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            //给这个对象一次性绑定两个事件
            myAddEvent(this.elements[i],'mouseover',fnover);
            myAddEvent(this.elements[i],'mouseout',fnout);
        }
    };
    
    //css方法
    dQuery.prototype.css=function(attr,value){
        if(arguments.length==2){//当参数个数为2时,使用设置css的方法
            var i=0;
            for(i=0;i<this.elements.length;i++){
                this.elements[i].style[attr]=value;
            }
        }else{//只有一个参数时获取样式
            return getStyle(this.elements[0],attr);
        }
    };
    
    //toggle方法:
    dQuery.prototype.toggle=function(){
        var _arguments=arguments;//把toggle的arguments存起来,以便在其它函数中可以调用。
    
        //私有计数器,计数器会被一组对象所享用。
        functionaddToggle(obj){
            var count=0;
            myAddEvent(obj,'click',function(){
                _arguments[count++%_arguments.length].call(obj);
            })
        }
    
        var i=0;
        for(i=0;i<this.elements.length;i++){
            addToggle(this.elements[i]);
        } 
    }
    
    
    //attr方法和css方法类似。
    dQuery.prototype.attr=function(attr,value){
        if(arguments.length==2){//设置属性
            var i=0;
    
            for(i=0;i<this.elements.length;i++){
                this.elements[i][attr]=value;
            }
        }else{//获取属性
            return this.elements[0][attr];
        }
    }
    
    //eq选择器
    dQuery.prototype.eq=function(n){
        return new dQuery(this.elements[n]);
    }
    
    
    //find选择器
    //定义一个小函数,两个数组(元素集合),把两个类数组(html元素集合)合并在一块。
    functionappendArr(arr1, arr2){
        var i=0;
    
        for(i=0;i<arr2.length;i++){
            arr1.push(arr2[i]);
        }
    }
    
    dQuery.prototype.find=function(str){
        var i=0;
        var aResult=[];//存放临时数据
    
        for(i=0;i<this.elements.length;i++){
            switch(str.charAt(0)){
    
                case '.'://class类
                    var aEle=getByClass(this.elements[i],str.substring(1));
                aResult.concat(aEle);//桥接到aResult内。但是
                break;
    
                default://其它标签名(TagName)
                    var aEle=this.elements[i].getElementsByTagName(str);
                    appendArr(aResult,aEle);
            }
        }
        var newdQuery=new dQuery();
        newdQuery.elements=aResult;
        return newdQuery;//保持可链。
    }
    
    //获取索引值函数
    functiongetIndex(obj){
        var aBrother=obj.parentNode.children;
        var i=0;
        
        for(i=0;i<aBrother.length;i++){
            if(aBrother[i]==obj){
                return i;
            }
        }
    }
    dQuery.prototype.index=function(){
        return getIndex(this.elements[0]);
    }
     
     
     
     

    本篇为完结篇。主要讲述如何造出轮子的高级特性。

    一. css方法的高级操作

    先看本文第一部分所讲的dQuery css方法

    //css方法
    dQuery.prototype.css=function(attr,value){
        if(arguments.length==2){//当参数个数为2时,使用设置css的方法
            var i=0;
            for(i=0;i<this.elements.length;i++){
                this.elements[i].style[attr]=value;
            }
        }else{//只有一个参数时获取样式
            return getStyle(this.elements[0],attr);
        }
    };

    在这个方法里,只能一次一行,且只能设置一个属性。效率还不够高。现在尝试通过设置类似$d('#div1').css({'200px',height:'200px',background:'red'})这样的方式,向css方法传入一个json对象。允许一次性设置多个css样式。或者允许链式操作。

    1. 传入json数据

    传入json实质上是一个参数。所以只有一个参数时,不仅仅是获取,还可能是传入了json数据。需要对传入一个参数时,进行分类讨论


    json与for-in循环
    var json={width:'200px',height:'200px',background:'red'};
    var i=0;
    
    for(i in json){
        alert(i+':'+json[i]);
    }

    这段程序可以依次弹出i(json属性):json数据的窗口。


    同理,在dQuery的css方法中也可以这么做。

    //css方法
    dQuery.prototype.css=function(attr,value){
        if(arguments.length==2){//当参数个数为2时,使用设置css的方法
            var i=0;
            for(i=0;i<this.elements.length;i++){
                this.elements[i].style[attr]=value;
            }
        }else if(arguments.length==1){//只有一个参数时获取样式,或传入json
            if(typeof attr=='string'){//attr为纯文字时,设置样式
                return getStyle(this.elements[0],attr);
            }else{//传入json,批量设置css样式
                for(i=0;i<this.elements.length;i++){
                    var j='';
    
                    for(j in attr){
                        this.elements[i].style[j]=attr[j];
                    }
                }
            }
        }
    };

    测试成功。

    #### (2)链式操作

    函数链式操作原理:返回函数本身。
    function show(str){
        alert(str);
        return show;//关键
    }
    show('abc')('bcd');

    这段代码将弹出两个框,abc,和bcd。只要你想,可以无限地写下去。


    链式操作是jquery的主要特色。执行完一个方法之后,又返回到该对象。所以只需要在css函数的最后,补上return this。就完成了。根据这个思想,可以给每个方法加上return this,那么你的js库就可以实现完全的链式操作。

    二. attr设置进一步——removeClass和addClass的实现

    之前说到attr方法本质上和css方法一样的。所以attr也可以用类似的方法设置——

    //attr方法和css方法类似。
    dQuery.prototype.attr=function(attr,value){
        if(arguments.length==2){//设置属性
            var i=0;
    
            for(i=0;i<this.elements.length;i++){
                this.elements[i][attr]=value;
            }
        }else if(arguments.length==1){//获取属性
            if(typeof attr=='string'){
                return this.elements[0][attr];
            }else{
                for(i=0;i<this.elements.length;i++){
                    var j='';
    
                    for(j in attr){
                        this.elements[i][j]=attr[j];
                    }
                }
            }
        }
        return this;
    }

    这段attr方法已经能够实现链式操作和接收json数据。现在需要利用attr方法实现添加class和移除class的功能。
    jquery中,addClassremoveClass的基本功能是:给addClass方法传入一个字符串,目标元素的class尾部追加这段字符串,给removeClass传入字符串,就把该字符串从目标元素的字符串中删除掉。
    用文字描述之后,实现就简单不少了。

    //addClass和removeClass的实现
    dQuery.prototype.addClass=function(str){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){
            if(this.elements[i].className==''){
                this.elements[i].className+=str;
            }else{
                this.elements[i].className+=' '+str;
            }
        }
        return this;
    }
    
    dQuery.prototype.removeClass=function(str){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){ 
            var _className=this.elements[i].className
            if(!str){//如果不传参,所有class都被清空。
                this.elements[i].className='';
            }else if(_className!=''){
                var arr=_className.split(' ');
                var j=0;
    
                for(j=0;j<arr.length;j++){
                    if(arr[j]==str){
                        arr.splice(j,1);//从数组第j个起删除1个(移除arr[j])
                        this.elements[i].className=arr.join(' ');//把数组重新转化为字符串,并用空格分开。最后赋值给当下对象的className。
                    }
                }
            }
        }
        return this;
    }

    注意,此段代码有局限性,前提是操作者html操作正确时才能生效,类似$d('.div1').addClass('div1').removeClass('div1')虽然也能尝试理解操作者意思并容错。但是<div class="div1 div1 div2">这样错误的class标记使用removeClass会发生错误。

    3.阻止冒泡及默认事件

    右键菜单(contextmenu)为例——jquery阻止默认事件,冒泡的机制是

    $(function(){
        $(document).bind('contextmenu',function(){//对document绑定contextmenu事件,并执行函数。
            return false;
        })
    })

    return false在原生js中只处理阻止默认事件,但jquery可以两样都阻止。
    首先,dQuery没有bind方法。加上去就行。所谓绑定事件的框架很简单,实际上和dQuery其它事件的框架是一样的。

    //绑定事件的方法:
    dQuery.prototype.bind=function(sEv,fn){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){
            myAddEvent(this.elements[i],sEv,fn);
        }
    }

    只要你在页面上右键点击,就会触发函数。然而还不完善。

    $d(function (){
        $d(document).bind('contextmenu', function (){
            return false;
        });
    });

    这里添加了return false,但右击,无法阻止任何事件。
    究其深层原因,可以发现,回调函数是通过myAddEvent()这个函数实现的。
    这就搞笑了。在myAddEvent()中,无论return什么都没有意义。所以要实现,完整的myAddEvent()代码是:

    //可重复调用的加载函数
    function myAddEvent(obj,sEv,fn){
        if(obj.attachEvent){
            obj.attachEvent('on'+sEv,function(){
                if(false==fn.call(obj)){//当调用return false的时候
                    event.cancelBubble=true;//阻止冒泡
                    return false;
                } 
            });
        }else{
            obj.addEventListener(sEv,function(ev){
                if(false==fn.call(obj)){
                    ev.cancelBubble=true;//阻止冒泡
                    ev.preventDefault();//火狐、chrome下用于阻止默认事件的语句
                } 
            },false);
        }
    }

    这样,在三大浏览器下,已经实现了事件的绑定调用return false无默认事件。并且阻止冒泡。

    三. 插件接口

    插件需要一个机制。可以通过它自定义方法名称,方法内容.
    假设页面有三个class为div1的div,扩展s一个size方法,获取某类页面元素的个数:

    dQuery.prototype.extend=function (name, fn){
        dQuery.prototype[name]=fn;
    };
    
    $d().extend('size',function(){
        alert(this.element.length);
    })
    
    //调用插件里的方法和内部方法无差异
    $d('.div1').size();

    弹出结果就为3.
    从这个角度说,插件机制编写方法似乎更加便捷了。

    1. animate运动框架

    jquery的运动形式为:xxx.animate('',目标)。目标是一个json数据。
    在运动学教程中有一个比较完美的运动框架startMove。startMove的json传入的值为最终运动状态。注意:和jquery不同,数值不带单位,透明度的量度为100而不是1.

    $d().extend('animate',function(json){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){
            console.log(this.elements);
            startMove(this.elements[i],json,function(){alert('hehe')})
        }
    
        function getStyle(obj, attr)
        {
            if(obj.currentStyle)
            {
                return obj.currentStyle[attr];
            }
            else
            {
                return getComputedStyle(obj, false)[attr];
            }
        }
        
        function startMove(obj,json,fn){
          
            clearInterval(obj.timer);
              
          
            obj.timer=setInterval(function(){
                var bStop= true;//标志着所有运动都结束了
         
         
                //遍历每个json属性
                for(var attr in json){
         
         
                    //取当前的属性对象
                    var iCur=0;
                    if(attr=='opacity'){
                        iCur=parseInt(parseFloat(getStyle(obj,attr))*100);
                    }else{
                        iCur=parseInt(getStyle(obj,attr));
                    }
                     
         
                    //定义速度值
                    var iSpeed=(json[attr]-iCur)/8;
                    iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);
                     
         
         
                    //检测停止:如果我发现某个值不等于目标点bStop就不能为true。
                    if(iCur!==json[attr]){
                        bStop=false;
                    }
                     
                     
         
                    //计算
                    if(attr=='opacity'){
                         obj.style[attr]=(iCur+iSpeed)/100;
                         obj.style.filter='alpha(opacity:'+(iSpeed+iCur)+')';
                    }else{
                            obj.style[attr]=iCur+iSpeed+'px';
                    }
                     
         
                }
         
                //检测是否停止,是的话关掉定时器
                if(bStop==true){
                    if(iCur==json[attr]){
                        clearInterval(obj.timer);
                        if(fn){fn();};  
                    }
                }
            },20)
        }
    });

    那么这个插件就写完了。

    【案例分析】上下滑动的幻灯片(选项卡)

    基本思路和之前的选项卡案例差不多。但是切换过程是上下滑动的。因此所有图片不能隐藏,应该是一张借一张纵向连接。运动是由一个大容器带着一起运动。垂直运动距离由index()值决定同时,有了addClass和removeClass方法,可以设置一个class='active'的样式。

      <div id="tab">
          <ul class="list_group">
              <li><a href="javascript:;">1</a></li>
              <li><a href="javascript:;">2</a></li>
              <li><a href="javascript:;">3</a></li>
              <li><a href="javascript:;">4</a></li>
          </ul>
          <div class="box">
            <div class="box2">
              <div class="content">
                  <img alt="1" src="images/1.jpg">
              </div>
              <div class="content">
                  <img alt="2" src="images/2.jpg">
              </div>
              <div class="content">
                  <img alt="3" src="images/3.jpg">
              </div>
              <div class="content">
                  <img alt="4" src="images/4.jpg">
              </div>
            </div>
          </div>
      </div>

    css

    *{
        margin:0;
        padding: 0;
    }
    ul{
        list-style: none;
    }
    a{text-decoration: none;}
    
    #tab{
         400px;
        margin:100px auto;
        position: relative;
    }
    .list_group li{
        float: left;
    }
    .list_group li a{
        display: block;
         40px;
        line-height: 30px;
        text-align: center;
    }
    .box{
        clear: both;
         402px;height: 302px;
        overflow: hidden;
        position: relative; 
    }
    .box2{
        position: absolute;
        top:0;
    
    }
    .content{
         402px;
        height: 302px;
    }
    .content img{
        border: 1px solid black;
         400px;height: 300px;
    }
    .active{
        background: #ccc;
    }

    javascript

    $d(function(){
        $d('li').click(function(){
            var index=$d(this).index();
            var moveLength=-302*index;
            $d('.box2').animate({top:moveLength});
            $d('li').removeClass('active');
            $d(this).addClass('active');
        })
    })

    效果:

    效果还是差强人意,可以做自动播放:

    $d().extend('size',function(){
        return this.elements.length;
    })//定义一个统计元素个数的dQuery插件
    
    $d(function(){
        var iNow=0;
        var timer=null;
    
        //定义一个函数指令,可以移动.box2 -302*iNow个单位。
        function tab(){
            var moveLength=-302*iNow;   
            $d('.box2').animate({top:moveLength});
            $d('li').removeClass('active');
            $d('li').eq(iNow).addClass('active');
        }
    
        //自动播放定义定时器内的函数
        function timerInner(){
            iNow++;
            if(iNow==$d('li').size()){
                iNow=0;
            }
            tab();
        }
        timer=setInterval(timerInner,1000);//调用定时器
    
        //点击事件函数
        $d('li').click(function(){  
            iNow=$d(this).index(); 
            tab();
        });
    
        //鼠标移入选项卡范围,关掉定时器timer,移出时再打开
        $d('#tab').hover(function(){
            clearInterval(timer);
        },function(){
            timer=setInterval(timerInner,1000)
        })
    
    })

    效果更加人性化了。

    小结:与前一个版本的dQuery库相比,写法更进一步。

    2.拖拽插件

    加入页面上有一个绝对定位的div#div1
    样式如下:

    #div1{
         100px;height: 100px;
        background: red;
        position: absolute;
    }

    引入方法是:

    $d().extend('drag', function (){
        var i=0;
        
        for(i=0;i<this.elements.length;i++){
            drag(this.elements[i]);
        }
        
        function drag(oDiv){//拖拽函数
            oDiv.onmousedown=function (ev){
                var oEvent=ev||event;
                var disX=oEvent.clientX-oDiv.offsetLeft;
                var disY=oEvent.clientY-oDiv.offsetTop;
                
                document.onmousemove=function (ev){
                    var oEvent=ev||event;
                    
                    oDiv.style.left=oEvent.clientX-disX+'px';
                    oDiv.style.top=oEvent.clientY-disY+'px';
                };
                
                document.onmouseup=function (){
                    document.onmousemove=null;
                    document.onmouseup=null;
                };
            };
        }
    });

    调用方法:

    $d('#div1').drag();

    一个简单到令人发指的效果就做好了。



    附录

    1.dQuery基本代码(dQuery.js)

    //可重复调用的加载函数
    function myAddEvent(obj,sEv,fn){
        if(obj.attachEvent){
            obj.attachEvent('on'+sEv,function(){
                if(false==fn.call(obj)){//当调用return false的时候
                    event.cancelBubble=true;
                    return false;
                } 
            });
        }else{
            obj.addEventListener(sEv,function(ev){
                if(false==fn.call(obj)){
                    ev.cancelBubble=true;
                    ev.preventDefault();//火狐、chrome下用于阻止默认事件的语句
                } 
            },false);
        }
    }
    
    
    
    //class选择器调用函数
    function getByClass(oParent,sClass){
        var aEle=oParent.getElementsByTagName('*');//选择父元素的所有元素
        var aResult=[];
        var re=new RegExp('\b'+sClass+'\b','i');//正则边界
        var i=0;
        for(i=0;i<aEle.length;i++){
            if(re.test(aEle[i].className)){
                aResult.push(aEle[i]);
            }
        }
        return aResult;
    }
    
    
    
    //获取计算后的样式
    function getStyle(obj,attr){
        //元素,样式
        if(obj.currentStyle){//兼容ie9及以下
            return obj.currentStyle[attr];
        }else{
            return getComputedStyle(obj,false)[attr];
        }
    }
    
    //定义dQuery对象
    function dQuery(vArg){//参数是变体变量
        this.elements=[];//选择器选择的元素扔到这个数组中
        switch(typeof vArg){
            //如果参数是函数
            case 'function':
                myAddEvent(window,'load',vArg);
                break;
            //如果参数是字符串
            case 'string':
                switch(vArg.charAt(0)){
                    case '#'://id选择器参数应该为#号之后的字符段
                        var obj=document.getElementById(vArg.substring(1));
                        this.elements.push(obj);
                    break;
    
                    case '.'://class
                        this.elements=getByClass(document,vArg.substring(1));
                        break;
    
                    default://标签
                        this.elements=document.getElementsByTagName(vArg);
                }
                break;
            //如果参数是对象。
            case 'object':
                this.elements.push(vArg);
                
        }
    }
    
    //定义简写
    function $d(vArg){
        return  new dQuery(vArg);
    }
    
    
    
    //对选择器函数绑定click事件
    dQuery.prototype.click=function(fn){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            myAddEvent(this.elements[i],'click',fn);
        }
        return this;
    }
    
    //对选择器函数绑定show/hide事件
    dQuery.prototype.show=function(){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            this.elements[i].style.display='block';
        }
        return this;
    }
    
    dQuery.prototype.hide=function(){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            this.elements[i].style.display='none';
        }
        return this;
    };
    
    //hover方法
    dQuery.prototype.hover=function(fnover,fnout){
        var i=0;
        //对于返回器数组的内容
        for(i=0;i<this.elements.length;i++){
            //给这个对象一次性绑定两个事件
            myAddEvent(this.elements[i],'mouseover',fnover);
            myAddEvent(this.elements[i],'mouseout',fnout);
        }
        return this;
    };
    
    //css方法
    dQuery.prototype.css=function(attr,value){
        if(arguments.length==2){//当参数个数为2时,使用设置css的方法
            var i=0;
            for(i=0;i<this.elements.length;i++){
                this.elements[i].style[attr]=value;
            }
        }else if(arguments.length==1){//只有一个参数时获取样式,或传入json
            if(typeof attr=='string'){//attr为纯文字时,设置样式
                return getStyle(this.elements[0],attr);
            }else{//传入json,批量设置css样式
                for(i=0;i<this.elements.length;i++){
                    var j='';
    
                    for(j in attr){
                        this.elements[i].style[j]=attr[j];
                    }
                }
            }       
        }
        return this;
    };
    
    //toggle方法:
    dQuery.prototype.toggle=function(){
        var _arguments=arguments;//把toggle的arguments存起来,以便在其它函数中可以调用。
    
        //私有计数器,计数器会被一组对象所享用。
        functionaddToggle(obj){
            var count=0;
            myAddEvent(obj,'click',function(){
                _arguments[count++%_arguments.length].call(obj);
            })
        }
    
        var i=0;
        for(i=0;i<this.elements.length;i++){
            addToggle(this.elements[i]);
        } 
        return this;
    }
    
    
    //attr方法和css方法类似。
    dQuery.prototype.attr=function(attr,value){
        if(arguments.length==2){//设置属性
            var i=0;
    
            for(i=0;i<this.elements.length;i++){
                this.elements[i][attr]=value;
            }
        }else if(arguments.length==1){//获取属性
            if(typeof attr=='string'){
                return this.elements[0][attr];
            }else{
                for(i=0;i<this.elements.length;i++){
                    var j='';
    
                    for(j in attr){
                        this.elements[i][j]=attr[j];
                    }
                }
            }
        }
        return this;
    }
    
    //addClass和removeClass的实现
    dQuery.prototype.addClass=function(str){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){
            
            if(this.elements[i].className==''){
                this.elements[i].className+=str;
            }else{
                this.elements[i].className+=' '+str;
            }
            
        }
        return this;
    }
    
    dQuery.prototype.removeClass=function(str){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){ 
            var _className=this.elements[i].className
            if(!str){//如果不传参,所有class都被清空。
                this.elements[i].className='';
            }else if(_className!=''){
                var arr=_className.split(' ');
                var j=0;
    
                for(j=0;j<arr.length;j++){
                    if(arr[j]==str){
                        arr.splice(j,1);//从数组第j个起删除1个(移除arr[j])
                        this.elements[i].className=arr.join(' ');//把数组重新转化为字符串,并用空格分开。最后赋值给当下对象的className。
                    }
                }  
            }        
        }
        return this;
    }
    
    //eq选择器
    dQuery.prototype.eq=function(n){
        return new dQuery(this.elements[n]);
    }
    
    
    //find选择器
    //定义一个小函数,两个数组(元素集合),把两个类数组(html元素集合)合并在一块。
    functionappendArr(arr1, arr2){
        var i=0;
    
        for(i=0;i<arr2.length;i++){
            arr1.push(arr2[i]);
        }
    }
    
    dQuery.prototype.find=function(str){
        var i=0;
        var aResult=[];//存放临时数据
    
        for(i=0;i<this.elements.length;i++){
            switch(str.charAt(0)){
    
                case '.'://class类
                    var aEle=getByClass(this.elements[i],str.substring(1));
                aResult.concat(aEle);//桥接到aResult内。但是
                break;
    
                default://其它标签名(TagName)
                    var aEle=this.elements[i].getElementsByTagName(str);
                    appendArr(aResult,aEle);
            }
        }
        var newdQuery=new dQuery();
        newdQuery.elements=aResult;
        return newdQuery;//保持可链。
    }
    
    //获取索引值函数
    functiongetIndex(obj){
        var aBrother=obj.parentNode.children;
        var i=0;
        
        for(i=0;i<aBrother.length;i++){
            if(aBrother[i]==obj){
                return i;
            }
        }
    }
    dQuery.prototype.index=function(){
        return getIndex(this.elements[0]);
    }
    
    
    //绑定事件的方法:
    dQuery.prototype.bind=function(sEv,fn){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){
            myAddEvent(this.elements[i],sEv,fn);
        }
    }
    
    //插件机制
    dQuery.prototype.extend=function (name, fn){
        dQuery.prototype[name]=fn;
    };

    2.动画插件

    注:动画插件可以设计类似jquery中fadeIn/Out,slideUp/Down 等等常见的动画效果。

    $d().extend('animate',function(json){
        var i=0;
    
        for(i=0;i<this.elements.length;i++){
            startMove(this.elements[i],json)
        }
    
        function getStyle(obj, attr)
        {
            if(obj.currentStyle)
            {
                return obj.currentStyle[attr];
            }
            else
            {
                return getComputedStyle(obj, false)[attr];
            }
        }
        
        function startMove(obj,json,fn){
          
            clearInterval(obj.timer);
              
          
            obj.timer=setInterval(function(){
                var bStop= true;//标志着所有运动都结束了
         
         
                //遍历每个json属性
                for(var attr in json){
         
         
                    //取当前的属性对象
                    var iCur=0;
                    if(attr=='opacity'){
                        iCur=parseInt(parseFloat(getStyle(obj,attr))*100);
                    }else{
                        iCur=parseInt(getStyle(obj,attr));
                    }
                     
         
                    //定义速度值
                    var iSpeed=(json[attr]-iCur)/8;
                    iSpeed=iSpeed>0?Math.ceil(iSpeed):Math.floor(iSpeed);
                     
         
         
                    //检测停止:如果我发现某个值不等于目标点bStop就不能为true。
                    if(iCur!==json[attr]){
                        bStop=false;
                    }
                     
                     
         
                    //计算
                    if(attr=='opacity'){
                         obj.style[attr]=(iCur+iSpeed)/100;
                         obj.style.filter='alpha(opacity:'+(iSpeed+iCur)+')';
                    }else{
                            obj.style[attr]=iCur+iSpeed+'px';
                    }
                     
         
                }
         
                //检测是否停止,是的话关掉定时器
                if(bStop==true){
                    if(iCur==json[attr]){
                        clearInterval(obj.timer);
                        if(fn){fn();};  
                    }
                }
         
         
            },20)
        }
    });

    3.拖拽插件

    $d().extend('drag', function (){
        var i=0;
        
        for(i=0;i<this.elements.length;i++){
            drag(this.elements[i]);
        }
        
        function drag(oDiv){//拖拽函数
            oDiv.onmousedown=function (ev){
                var oEvent=ev||event;
                var disX=oEvent.clientX-oDiv.offsetLeft;
                var disY=oEvent.clientY-oDiv.offsetTop;
                
                document.onmousemove=function (ev){
                    var oEvent=ev||event;
                    
                    oDiv.style.left=oEvent.clientX-disX+'px';
                    oDiv.style.top=oEvent.clientY-disY+'px';
                };
                
                document.onmouseup=function (){
                    document.onmousemove=null;
                    document.onmouseup=null;
                };
            };
        }
    });
     
  • 相关阅读:
    编译用到boost相关的东西,问题的解决;以及和googletest库
    看开源代码利器—用Graphviz + CodeViz生成C/C++函数调用图(call graph)
    centos5 升级到centos6
    Go vs Erlang
    Graphviz
    Oracle相关安装经验总结
    学习erlang书籍
    sublime使用总结
    List集合五种遍历方式
    nginx常用命令
  • 原文地址:https://www.cnblogs.com/libin-1/p/6018340.html
Copyright © 2020-2023  润新知