"从零开始写一款javascript插件(一)——tab选项卡"


title: "从零开始写一款javascript插件(一)——tab选项卡" layout: post category: 技术-前端 date: 2016-06-15 16:00:00 +0800 author: hjb2722404 tags: javascript插件,选项卡 status: publish


上一次写了《从零开始学习制作H5场景应用》系列,反响还算好,所以趁最近不是太忙,继续新的系列,从零开始写javascript插件。

今天,先从最简单的开始,将已有的一个Tab选项卡切换功能改写成javascript插件形式。

原生函数写法

将一个javascript方法改写为js插件最简单的方式就是将这个方法挂载到window全局对象下面

我们先来看看最原始的使用函数写法的代码:

tab.html

    <!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <meta name="renderer" content="webkit">
    <title>jquery_hjb_tab插件demo</title>
    <link rel="stylesheet" href="h.css"/>
</head>
<body>

<div id="tab">
    <div class="tabs">
        <ul>
            <li><a href="#">tab1</a></li>
            <li><a href="#">tab2</a></li>
            <li><a href="#">tab3</a></li>
            <li><a href="#">tab4</a></li>
        </ul>
    </div>
    <div class="tabCons">
        <section>内容一</section>
        <section>内容二</section>
        <section>内容三</section>
        <section>内容四</section>
    </div>
</div>

<script>
    window.onload = h_tab('tab');
   function h_tab(tabId){
       var oLinks = document.getElementById(tabId).getElementsByTagName("a");
       var oCons = document.getElementById(tabId).getElementsByTagName("section");
       for(var i = 0; i<oLinks.length; i++){
           oLinks[i].index = i;
           oLinks[i].onclick = function () {
               for(var j =0; j<oLinks.length; j++){
                   oLinks[j].className="";
                   oCons[j].style.display = "none";
               }
               this.className="cur";
               oCons[this.index].style.display ="block";
           }

       }
   }
</script>

h.css

    @charset "utf-8";
/*
//author:hjb2722404
//description:
//date:2016/2/18
*/
.tabs ul { width: 100%; list-style-type: none;}
.tabs ul li { width: 48%; display: inline-block; margin: 0; padding: 0;}
.tabs ul li a {border-bottom: 3px solid #cccccc; width: 100%; height: 100%; display: block; text-align: center; min-height: 40px; line-height: 40px; text-decoration: none; font-family: "微软雅黑"; color: #a94442}
.tabs ul li a.cur { border-bottom: 3px solid #f26123;}
.tabCons section { display: none;}
.tabCons section:nth-child(1) { display: block;}

上面两份代码为基本代码,之后我们后一步步在这份代码的基础上进行改进。

原生插件写法

好,现在,我们就来将这个方法改写成挂载在window对象下的插件:

tab.html

    ……
// 下面是第一次改动
<script type="text/javascript" src="h_tabs.js"></script>
<script>
    H_tab("tab");
</script>
</body>
</html>

h_tabs.js

window.H_tab = function(tabId){
       var oLinks = document.getElementById(tabId).getElementsByTagName("a");
       var oCons = document.getElementById(tabId).getElementsByTagName("section");
       for(var i = 0; i<oLinks.length; i++){
           oLinks[i].index = i;
           oLinks[i].onclick = function () {
               for(var j =0; j<oLinks.length; j++){
                   oLinks[j].className="";
                   oCons[j].style.display = "none";
               }
               this.className="cur";
               oCons[this.index].style.display ="block";
           }

       }
   };

但是,我们发现这样的写法虽然很简单,但也有问题:window作为一个全局对象,如果我们把自己的方法都挂载到它下面作为插件使用的话,插件一多,就容易产生命名空间冲突,另一方面,使用原生js虽然可以减少对外部的依赖,但代码量相对还是很大,写法比较繁琐。

jquery写法

我们尝试引入jquery库,将此插件改写为jquery插件。

jquery插件有三种形式:类级别的形式,对象级别的形式,jquery UI组件的形式

jquery 类级别插件写法–单个方法

我们先来看类级别插件的形式。

第一种类级别插件的形式,直接把该方法挂载到jquery的根空间下,作为一个工具方法:

tab.html

……
<script src="../jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="h_tabs.js"></script>
<script>
    $.h_tab('tab');
</script>
</body>
</html>

h_tabs.js

    $.h_tab = function(tabId){
    var oLinks = document.getElementById(tabId).getElementsByTagName("a");
    var oCons = document.getElementById(tabId).getElementsByTagName("section");
    for(var i = 0; i<oLinks.length; i++){
        oLinks[i].index = i;
        oLinks[i].onclick = function () {
            for(var j =0; j<oLinks.length; j++){
                oLinks[j].className="";
                oCons[j].style.display = "none";
            }
            this.className="cur";
            oCons[this.index].style.display ="block";
        }

    }
};

jquery类级别插件写法-多个方法

如果你想要将多个方法绑定到jquery根空间上面,那么像下面这样写:

tab.html

……
<script src="../jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="h_tabs.js"></script>
<script>
    $.h_tab('tab');
    $.h_hello('hjb');
</script>
</body>
</html>

h_tabs.js

    $.extend({
    h_tab:function(tabId){
        var oLinks = document.getElementById(tabId).getElementsByTagName("a");
        var oCons = document.getElementById(tabId).getElementsByTagName("section");
        for(var i = 0; i<oLinks.length; i++){
            oLinks[i].index = i;
            oLinks[i].onclick = function () {
                for(var j =0; j<oLinks.length; j++){
                    oLinks[j].className="";
                    oCons[j].style.display = "none";
                }
                this.className="cur";
                oCons[this.index].style.display ="block";
            }

        }
    },
    h_hello :function(name){
        console.log("hello,",name);
    }
});

虽然使用$.extend()工具方法将自己的功能函数直接挂载到jquery根命名空间下,简单,省事儿,但很不幸的是,这样的方式不能利用jquery强大的sizzle引擎,即你选择到的DOM元素无法运用这个方法。

所以我们要用到对象级别的插件开发方式。

jquery对象级别插件写法

对象级别的插件开发方式是利用$.fn.extend()方法将自己的方法绑定到jquery原型上去,这样所有jquery对象队可以应用该方法来执行相应操作了

代码如下:

tab.html

……
<script src="../jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="h_tabs.js"></script>
<script>
    //对象级别的插件引用方法,注意和上面类级别插件的写法上的区分
    $('#tab').h_tab('tab');
</script>
</body>
</html>

h_tabs.js

(function($){
    $.fn.extend({
        h_tab:function(tabId){
            var oLinks = document.getElementById(tabId).getElementsByTagName('a');
            var oCons = document.getElementById(tabId).getElementsByTagName('section');
            for(var i = 0; i<oLinks.length; i++){
                oLinks[i].index = i;
                oLinks[i].onclick = function () {
                    for(var j =0; j<oLinks.length; j++){
                        oLinks[j].className="";
                        oCons[j].style.display = "none";
                    }
                    this.className="cur";
                    oCons[this.index].style.display ="block";
                }

            }
        }
    });
})(jQuery);

这里,我们利用一个闭包封装了插件,避免了命名空间污染

在这里,还有一些问题,就是我们的方法必须传参数才可以运行,这就导致调用的时候我们使用$(‘#tab’)选择了id为tab的div,然后在插件里我们又根据传入的ID获取了一遍该元素。

既然我们已经使用了jquery的选择器,那么我们就可以引入this来解决重复获取元素的冗余问题。

jquery对象级别插件写法-引入this

tab.html

……
<script src="../jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="h_tabs.js"></script>
<script>
    $('#tab').h_tab();
</script>
</body>
</html>

h_tabs.js

(function($){
    $.fn.extend({
        h_tab:function(){
            //在这里引入this
            var oLinks = this.find('a');
            var oCons = this.find('section');
            for(var i = 0; i<oLinks.length; i++){
                oLinks[i].index = i;
                oLinks[i].onclick = function () {
                    for(var j =0; j<oLinks.length; j++){
                        oLinks[j].className="";
                        oCons[j].style.display = "none";
                    }
                    this.className="cur";
                    oCons[this.index].style.display ="block";
                }

            }
        }
    });
})(jQuery);

这里需要注意的是,我们调用该插件的元素对象是(tab)使this.find()(‘tab’).find(),而不是$(this).find(),注意使用代入法来区分这两种写法的差别。

作为一款插件,它应该是可以被开发者控制的,所以还应该提供给使用者一些配置接口。

jquery对象级别插件写法-加入配置项

tab.html

    ……
        <ul>
        <!--对照文章开始的代码, 注意这里的改动 -->
            <li><a href="#" class="current">tab1</a></li>
            <li><a href="#">tab2</a></li>
     ……
<script src="../jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="h_tabs.js"></script>
<script>
    $('#tab').h_tab({
        //使得当前选项卡标签的样式名称可自定义的配置
        curName:'current'
    });
</script>
</body>
</html>

我们这里把一开始的”当前选项卡标签样式类名称“由”cur“改为了”current“,并将其作为配置项传入插件

h.css

.tabs ul { width: 100%; list-style-type: none;}
.tabs ul li { width: 48%; display: inline-block; margin: 0; padding: 0;}
.tabs ul li a {border-bottom: 3px solid #cccccc; width: 100%; height: 100%; display: block; text-align: center; min-height: 40px; line-height: 40px; text-decoration: none; font-family: "微软雅黑"; color: #a94442}

/*注意下面一行与之前的样式代码的对比变化之处*/
.tabs ul li a.current { border-bottom: 3px solid #f26123;}

.tabCons section { display: none;}
.tabCons section:nth-child(1) { display: block;}

我们在样式表中做出了相应的改动。

h_tabs.js

    (function($){
    $.fn.extend({
        //给方法传入一个对象作为参数
        h_tab:function(opts){
        //定义默认的配置
            var defaults ={
                curName : 'cur'
            };

            //将传入的参数覆盖默认参数中的默认项,最终合并到一个新的参数对象上
            var Opt = $.extend({},defaults,opts);

            var oLinks = this.find('a');
            var oCons = this.find('section');
            for(var i = 0; i<oLinks.length; i++){
                oLinks[i].index = i;
                oLinks[i].onclick = function () {
                    for(var j =0; j<oLinks.length; j++){
                        oLinks[j].className="";
                        oCons[j].style.display = "none";
                    }

                    //在这里使用配置项的值
                    this.className = Opt['curName'];

                    oCons[this.index].style.display ="block";
                }

            }
        }
    });

})(jQuery);

在这里我们使用了jquery的$.extend()方法的合并对象的功能,使用用户传入的配置项覆盖默认配置项并最终合并到一个新的配置项供后面的程序使用。

好了,经过了这么几步,我们基本上完成了一款最简单的javascript插件。所有代码已经上传至我的github上面,下面是地址:

git代码仓库查看源码