zoukankan      html  css  js  c++  java
  • 仿照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;
                };
            };
        }
    });
    
  • 相关阅读:
    UVA 254 Towers of Hanoi
    UVA 701 The Archeologists' Dilemma
    UVA 185 Roman Numerals
    UVA 10994 Simple Addition
    UVA 10570 Meeting with Aliens
    UVA 306 Cipher
    UVA 10160 Servicing Stations
    UVA 317 Hexagon
    UVA 10123 No Tipping
    UVA 696 How Many Knights
  • 原文地址:https://www.cnblogs.com/djtao/p/6018097.html
Copyright © 2011-2022 走看看