仿照jquery封装一个自己的js库(二)

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

一. 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={'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存起来,以便在其它函数中可以调用。

    //私有计数器,计数器会被一组对象所享用。
    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]);
    } 
    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元素集合)合并在一块。
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;//保持可链。
}

//获取索引值函数
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]);
}


//绑定事件的方法:
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;
            };
        };
    }
});
原文地址:https://www.cnblogs.com/djtao/p/6018097.html