jQuery寫法

 

 

如何寫一個完善的 jquery ui 組件

時間 2016-08-05 10:47:15  稀土掘金javascript

原文  http://liuyy.coding.me/2016/03/30/javscript/如何寫一個完善的jquery ui組件/java

主題 jQuery UIjquery

在平常工做中,經常會遇到下面這樣的需求git

咱們須要對上面的tab作處理,點擊相應的tab就顯示相應的內容github

不封裝組件寫法

最原始的寫法緩存

<div class="tab-container">
    <ul class="nav nav-tabs">
        <li name="tab1" class="active"><a href="javascript:;">tab1</a></li>
        <li name="tab2"><a href="javascript:;">tab2</a></li>
        <li name="tab3"><a href="javascript:;">tab3</a></li>
    </ul>
    <div id="tab1">this is tab1</div>
    <div id="tab2" style="display: none;">this is tab2</div>
    <div id="tab3" style="display: none;">this is tab3</div>
</div>
$(function() {
    $('.tab-container').on('click', 'li', function() {
        var tabIds = ['tab1', 'tab2', 'tab3'];
        var id = $(this).attr('name');
        //便利全部的tabid,判斷是當前點擊的id 就顯示,不然隱藏
        $.each(tabIds, function(i, el) {
            if (id === el) {
                $('.tab-container li[name=' + el + ']').addClass('active');
                $('#' + el).show();
            } else {
                $('.tab-container li[name=' + el + ']').removeClass('active');
                $('#' + el).hide();
            }
        });
    });
});

經過 li 標籤的事件綁定,每次都便利全部的tab,顯示點擊的,隱藏其餘的,這種作法能夠實現tab 切換的問題可是還有幾個缺點。dom

  1. 沒法重用,當另外一個地方須要使用到時只能把代碼copy過去
  2. 一個頁面屢次使用會有問題, 全部的選擇器都是在全局上查找,當一個頁面有兩個或者三個tabs的時候會有bug
  3. 每一次執行顯示的時候對其餘的tab無論它本來的撞他,都會執行隱藏操做,這是沒必要要的

重用————使用jquery插件封裝

爲了解決上面的問題,咱們把這種邏輯封裝成一個tabs插件, jquery對插件的支持使用 $.fnjquery插件

$.fn = jQuery.prototype; //$.fn執行jQuery對象的原型

代碼以下:ide

$.fn.tabs = function(){
    var $element = this;//上下文
    var showName = null;//當前顯示tab 儲存
    //初始化操做
    $element.find('div').hide();//現將全部隱藏掉
    $element.find('.nav li').removeClass('active');//消除全部的選擇狀態
    showName = $element.find('.nav li:eq(0)').attr('name');//初始化設置顯示第一個tab
    //事件監聽
    $element.on('click', 'li', function(){
        show($(this).attr('name'));
    });
    show();
    //顯示邏輯
    function show(name){
        if(name === showName) return;
        if(name){
            //先隱藏原來的
            hide(showName);
            showName = name;
        }
        //顯示當前的
        $('#' + showName, $ ).show();
        $('.nav li[name=' + showName + ']', $element).addClass('active');
    }
    function hide(name){
        //jquery方法的第二個參數表示在哪一個dom搜索,默認爲document
        $('#' + name, $element).hide();
        $('.nav li[name=' + name + ']', $element).removeClass('active');
    }
};

$(function() {
   $('.tab-container').tabs();
});

這樣實現以後,再別的地方須要調用只只須要調用 tabs 方法便可,可是,這種實現仍然是粗糙的。 show 和 hide 方法在每次調用tabs時都會從新建立,這樣比較消耗內存。性能

屢次使用————面向對象方式處理

用面向對象處理,將一些方法和屬性放在原型中處理,更加高效。在編碼以前,咱們須要對這個組件進行分析,整個 tabs 由兩個部分組成,一個導航的菜單 menu , 一個是與之對應的內容(就是下面那個div) panel , 能夠把一個 menu 和 panel 組成一個對象 tab , 而 tab 有兩個方法 show 和 hide 控制它的顯示和隱藏,而咱們的 tabs 就是 tab 對象的集合,控制着每個tab的顯示和隱藏.

function Tabs($element){
    //初始化每個tab對象
    this.tabs = {};//儲存tab
    var self = this;
    $('div', $element).hide();//現將全部隱藏掉
    $('.nav li', $element).removeClass('active');//消除全部的選擇狀態
    this.showName = $('.nav li:eq(0)', $element).attr('name');//初始化設置顯示第一個tab
    $('.nav li', $element).each(function(){
        var name = $(this).attr('name');
        var $menu = $(this);
        var $panel = $('#' + name, $element);
        var tab = new Tab($menu, $panel);
        self.tabs[name] = tab;//緩存tab
    });
    //事件監聽
    $element.on('click', '.nav li', function(){
        self.show($(this).attr('name'));
    });
    this.show();
}
Tabs.prototype.show = function(name){
    if(name === this.showName) return;
    if(name){
        this.tabs[this.showName].hide();//隱藏原來的
        this.showName = name;
    }
    this.tabs[this.showName].show();//顯示當前的
};
function Tab($menu, $panel){
    this.$menu = $menu;
    this.$panel = $panel;
}

Tab.prototype.show = function(){
    this.$menu.addClass('active');
    this.$panel.show();
};

Tab.prototype.hide = function(){
    this.$menu.removeClass('active');
    this.$panel.hide();
};

$.fn.tabs = function(){
   new Tabs(this);
};

用面向對象方法編寫的組件,擁有了不錯的性能,並且在代碼邏輯上更爲清晰,理解起來比以前的寫法要複雜一點。

可配置————options處理

再上面的代碼中咱們能夠看到,全部的tabs組件開始都是顯示的第一個,若是,咱們想在最初顯示第二個或是第三個,這個組件就沒法知足咱們的需求,由於它尚未加入參數(options)的處理。

一般在封裝時將可變,與核心邏輯無關的屬性,抽取出來成爲這個組件 options , 例如 tabs 組件中,它默認顯示的tab頁,以及選中tab時li上的樣式,這些咱們均可以提取出來,以保證的組件的靈活性

Tabs.defaults = {
    showName : null,             //當前顯示tab名稱
    activeClass : 'active',      //當tab被選擇時的樣式
    menuSelector : '.nav li',    //找到menu的選擇器
    menuAttr : 'name'           //指定menu上指定屬於哪一個tab名稱的屬性
}; //存儲組件默認的參數
function Tabs($element, options){
    //初始化每個tab對象
    this.tabs = {};//儲存tab
    this.$element = $element;
    var op = this.options = $.extend({}, Tabs.defaults, options);//初始化參數

    var self = this;
    $('div', $element).hide();//現將全部隱藏掉
    $(op.menuSelector, $element).removeClass(op.activeClass);//消除全部的選擇狀態
    if(!op.showName)
        this.showName = $(op.menuSelector + ':eq(0)', $element).attr(op.menuAttr);//初始化設置顯示第一個tab
    else
        this.showName = op.showName;
    var attrName = op.menuAttr;
    $('.nav li', $element).each(function(){
        var name = $(this).attr(attrName);
        var $menu = $(this);
        var $panel = $('#' + name, $element);
        var tabOp = {
            activeClass : op.activeClass
        };
        var tab = new Tab($menu, $panel, tabOp);
        self.tabs[name] = tab;//緩存tab
    });
    //事件監聽
    $element.on('click', op.menuSelector, function(){
        self.show($(this).attr(attrName));
    });
    this.show();
}
Tabs.prototype.show = function(name){
    if(name === this.showName) return;
    if(name){
        this.tabs[this.showName].hide();//隱藏原來的
        this.showName = name;
    }
    this.tabs[this.showName].show();//顯示當前的
};
function Tab($menu, $panel, tabOp){
    this.$menu = $menu;
    this.$panel = $panel;
    this.options = tabOp;
}

Tab.prototype.show = function(){
    this.$menu.addClass(this.options.activeClass);
    this.$panel.show();
};

Tab.prototype.hide = function(){
    this.$menu.removeClass(this.options.activeClass);
    this.$panel.hide();
};

$.fn.tabs = function(options){
   new Tabs(this, options);
};

$(function() {
   $('.tab-container').tabs({
        showName : 'tab2'
   });
});

暴露組件內部動做————事件處理

加入了參數處理以後,組件就有了不錯的靈活性,可是,組件內部發生改變時,再外面沒法得知,假如咱們有這樣一個需求,再某個 tab 顯示的時候,初始化這個tab的數據,這個時候咱們就須要給組件假如事件處理,當內部發生改變的時候,觸發某個事件,外部能夠監聽事件來觀察組件的變化,作相應的處理

Tabs.prototype.show = function(name){
    if(name === this.showName) return;
    if(name){
        this.tabs[this.showName].hide();//隱藏原來的
        this.showName = name;
    }
    this.tabs[this.showName].show();//顯示當前的
    this.$element.trigger('tabs.show', this.showName);//使用jQuery的事件處理機制對於組件自定義事件通常都是,組件名稱.事件名稱
};

$('.tab-container').on('tabs.show', function(e, name) {
    alert(name);
});

提供方法給外部調用

不少的時候組件也會有一些方法讓外部調用,已達到手動改變組件狀態的效果,例如咱們須要再點擊一個按鈕以後顯示 tab2 ,這種時候咱們須要修改定義插件那部分的代碼,以暴露方法給外部調用

$.fn.tabs = function(options){
    if($.type(options) === 'object'){
        //實例化
        var tabs = new Tabs();
        this.data('tabs', tabs);
    }else{
        var tabs = this.data('tabs');
        var returnVal;
        if(tabs[options] && $.isFunction(tabs[options])){
            returnVal = tabs[options].call(tabs, Array.prototype.slice.call(arguments,1));
        }
        if(returnVal) return returnVal;
    }
    return this;
}
//調用方法
$('.tab-container').tabs('show', 'tab3');

到這一個完善的組件就構建完成啦,再編寫組件中還有一些須要注意的:

  1. 經常使用的dom或是jq最好是做爲組件屬性緩存起來,這樣能夠減小dom重複查找所消耗的時間;
  2. 一個組件要有 detory 在組件移除是要釋放掉內存和事件綁定;
  3. 每次組件內部狀態發生改變都要觸發事件,這樣可讓組件更加靈活;
  4. 不要阻止組件內部元素事件冒泡

最後

我本身有封裝一個jquery插件來定義組件,把組件定義經常使用的操做例如事件、外部調用方法,等等都有封裝

jquery-component

用jquery-component定義組件:

$.component('tabs', {
    //默認參數
    options: {
        showName: null, //當前顯示tab名稱
        activeClass: 'active', //當tab被選擇時的樣式
        menuSelector: '.nav li', //找到menu的選擇器
        menuAttr: 'name' //指定menu上指定屬於哪一個tab名稱的屬性
    },
    //實例化調用方法
    init: function() {
        //初始化每個tab對象
        this.tabs = {}; //儲存tab
        var $element = this.$element;
        var self = this;
        var op = this.options;
        $('div', $element).hide(); //現將全部隱藏掉
        $(op.menuSelector, $element).removeClass(op.activeClass); //消除全部的選擇狀態
        if (!op.showName)
            this.showName = $(op.menuSelector + ':eq(0)', $element).attr(op.menuAttr); //初始化設置顯示第一個tab
        else
            this.showName = op.showName;
        var attrName = op.menuAttr;
        $('.nav li', $element).each(function() {
            var name = $(this).attr(attrName);
            var $menu = $(this);
            var $panel = $('#' + name, $element);
            var tabOp = {
                activeClass: op.activeClass
            };
            var tab = new Tab($menu, $panel, tabOp);
            self.tabs[name] = tab; //緩存tab
        });
        //事件監聽
        $element.on('click', op.menuSelector, function() {
            self.show($(this).attr(attrName));
        });
        this.show();
    },
    show: function(name) {
        if (name === this.showName) return;
        if (name) {
            this.tabs[this.showName].hide(); //隱藏原來的
            this.showName = name;
        }
        this.tabs[this.showName].show(); //顯示當前的
        this._trigger('show', this.showName);//對事件的封裝
    },
    destory : function(){
        //當元素被銷燬的時候回自動調用這個方法
    }
});

function Tab($menu, $panel, tabOp) {
    this.$menu = $menu;
    this.$panel = $panel;
    this.options = tabOp;
}

Tab.prototype.show = function() {
    this.$menu.addClass(this.options.activeClass);
    this.$panel.show();
};

Tab.prototype.hide = function() {
    this.$menu.removeClass(this.options.activeClass);
    this.$panel.hide();
};

$(function() {
    $('.tab-container').tabs({
        showName: 'tab2'
    });
    $('.tab-container').on('tabs.show', function(e, name) {
        alert(name);
    });
});
相關文章
相關標籤/搜索