深入理解JQuery插件開發

来源:http://www.cnblogs.com/lslvxy/archive/2016/04/20/5411236.html
-Advertisement-
Play Games

如果你看到這篇文章,我確信你毫無疑問會認為jQuery是一個使用簡便的庫。jQuery可能使用起來很簡單,但是它仍然有一些奇怪的地方,對它基本功能和概念不熟悉的人可能會難以掌握。但是不用擔心,我下麵已經把代碼劃分成小部分,做了一個簡單的指導。那些語法看起來可能過於複雜,但是如果進入到它的思想和模式中 ...


如果你看到這篇文章,我確信你毫無疑問會認為jQuery是一個使用簡便的庫。jQuery可能使用起來很簡單,但是它仍然有一些奇怪的地方,對它基本功能和概念不熟悉的人可能會難以掌握。但是不用擔心,我下麵已經把代碼劃分成小部分,做了一個簡單的指導。那些語法看起來可能過於複雜,但是如果進入到它的思想和模式中,它是非常簡單易懂的。

下麵,我們有了一個插件的基本層次:

// Shawn Khameneh
// ExtraordinaryThoughts.com

(function($) {
    var privateFunction = function() {

// 代碼在這裡運行
    }

    var methods = {
        init: function(options) {
            return this.each(function() {
                var $this = $(this);
                var settings = $this.data('pluginName');

                if(typeof(settings) == 'undefined') {

                    var defaults = {
                        propertyName: 'value',
                        onSomeEvent: function() {}
                    }

                    settings = $.extend({}, defaults, options);

                    $this.data('pluginName', settings);
                } else {
                    settings = $.extend({}, settings, options);
                }


// 代碼在這裡運行

            });
        },
        destroy: function(options) {
            return $(this).each(function() {
                var $this = $(this);

                $this.removeData('pluginName');
            });
        },
        val: function(options) {
            var someValue = this.eq(0).html();

            return someValue;
        }
    };

    $.fn.pluginName = function() {
        var method = arguments[0];

        if(methods[method]) {
            method = methods[method];
            arguments = Array.prototype.slice.call(arguments, 1);
        } else if( typeof(method) == 'object' || !method ) {
            method = methods.init;
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.pluginName' );
            return this;
        }

        return method.apply(this, arguments);

    }

})(jQuery);

你可能會註意到,我所提到代碼的結構和其他插件代碼有很大的不同。根據你的使用和需求的不同,插件的開發方式也可能會呈現多樣化。我的目的是澄清代碼中的一些概念,足夠讓你找到適合自己的方法去理解和開發一個jQuery插件。

現在,來解剖我們的代碼吧!

容器:一個即時執行函數

根本上來說,每個插件的代碼是被包含在一個即時執行的函數當中,如下:

(function(arg1, arg2) {

// 代碼
})(arg1, arg2);

即時執行函數,顧名思義,是一個函數。讓它與眾不同的是,它被包含在一對小括弧裡面,這讓所有的代碼都在匿名函數的局部作用域中運行。這並不是說DOM(全局變數)在函數內是被屏蔽的,而是外部無法訪問到函數內部的公共變數和對象命名空間。這是一個很好的開始,這樣你聲明你的變數和對象的時候,就不用擔心著變數名和已經存在的代碼有衝突。

現在,因為函數內部所有的所有公共變數是無法訪問的,這樣要把jQuery本身作為一個內部的公共變數來使用就會成為問題。就像普通的函數一樣,即時函數也根據引用傳入對象參數。我們可以將jQuery對象傳入函數,如下:

(function($) {


// 局部作用域中使用$來引用jQuery
})(jQuery);

我們傳入了一個把公共變數“jQuery”傳入了一個即時執行的函數裡面,在函數局部(容器)中我們可以通過“$”來引用它。也就是說,我們把容器當做一個函數來調用,而這個函數的參數就是jQuery。因為我們引用的“jQuery”作為公共變數傳入,而不是它的簡寫“\(”,這樣我們就可以相容Prototype庫。如果你不用Prototype或者其它用`“\)”`做簡寫的庫的話,你不這樣做也不會造成什麼影響,但是知道這種用法仍是一件好事。

插件:一個函數

一個jQuery插件本質上是我們塞進jQuery命名空間中一個龐大的函數,當然,我們可以很輕易地用“jQuery.pluginName=function”,來達到我們的目的,但是如果我們這樣做的話我們的插件的代碼是處於沒有被保護的暴露狀態的。“jQuery.fn”是“jQuery.prototype”的簡寫,意味當我們通過jQuery命名空間去獲取我們的插件的時候,它僅可寫(不可修改)。它事實上可以為你乾點什麼事呢?它讓你恰當地組織自己的代碼,和理解如何保護你的代碼不受運行時候不需要的修改。最好的說法就是,這是一個很好的實踐!

通過一個插件,我們獲得一個基本的jQuery函數:


(function($) {

    $.fn.pluginName = function(options) {


// 代碼在此處運行

        return this;
    }

})(jQuery);

上面的代碼中的函數可以像其他的jQuery函數那樣通過
$('#element’).pluginName()來調用。註意,我是如何把“return this”語句加進去的;這小片的代碼通過返回一個原來元素的集合(包含在this當中)的引用來產生鏈式調用的效果,而這些元素是被一個jQuery對象所包裹的。你也應該註意,“this”在這個特定的作用域中是一個jQuery對象,相當於$(‘#element’)

根據返回的對象,我們可以總結出,在上面的代碼中,使用$(‘#element’).pluginName()的效果和使用$(‘#element’)的效果是一樣的。在你的即時執行函數作用域中,沒必要用$(this)的方式來把this包裹到一個jQuery對象中,因為this本身已經是被包裝好的jQuery對象。

多個元素:理解Sizzle

jQuery使用的選擇器引擎叫Sizzle,Sizzle可以為你的函數提供多元素操作(例如對所有類名相同的元素)。這是jQuery幾個優秀的特性之一,但這也是你在開發插件過程中需要考慮的事情。即使你不准備為你的插件提供多元素支持,但為這做準備仍然是一個很好的實踐。

這裡我添加了一小段代碼,它讓你的插件代碼為多元素集合中每個元素單獨地起作用:

function($) {


// 向jQuery中被保護的“fn”命名空間中添加你的插件代碼,用“pluginName”作為插件的函數名稱
    $.fn.pluginName = function(options) {


// 返回“this”(函數each()的返回值也是this),以便進行鏈式調用。
        return this.each(function() {


// 此處運行代碼,可以通過“this”來獲得每個單獨的元素

// 例如: $(this).show();
            var $this = $(this);

        });

    }

})(jQuery);

在以上示例代碼中,我並不是用 each()在我的選擇器中每個元素上運行代碼。在那個被 each()調用的函數的局部作用域中,你可以通過this來引用每個被單獨處理的元素,也就是說你可以通過\((this)來引用它的jQuery對象。在局部作用域中,我用\)this變數存儲起jQuery對象,而不是每次調用函數的時候都使用$(this),這會是個很好的實踐。當然,這樣做並不總是必要的;但我已經額外把它包含在我的代碼中。還有要註意的是,我們將會對每個單獨方法都使用 each(),這樣到時我們就可以返回我們需要的值,而不是一個jQuery對象。

下麵是一個例子,假如我們的插件支持一個 val 的方法,它可以返回我們需要的值:

$('#element').pluginName('val');
// 會返回我們需要的值,而不是一個jQuery對象

功能:公有方法和私有方法

一個基本的函數可能在某些情況下可以良好地工作,但是一個稍微複雜一點的插件就需要提供各種各樣的方法和私有函數。你可能會使用不同的命名空間去為你的插件提供各種方法,但是最好不要讓你的源代碼因為多餘的命名空間而變得混亂。

下麵的代碼定義了一個存儲公有方法的JSON對象,以及展示瞭如何使用插件中的主函數中去判斷哪些方法被調用,和如何在讓方法作用到選擇器每個元素上。

(function($) {


// 在我們插件容器內,創造一個公共變數來構建一個私有方法
    var privateFunction = function() {

// code here
    }


// 通過字面量創造一個對象,存儲我們需要的共有方法
    var methods = {

// 在字面量對象中定義每個單獨的方法
        init: function() {


// 為了更好的靈活性,對來自主函數,併進入每個方法中的選擇器其中的每個單獨的元素都執行代碼
            return this.each(function() {

// 為每個獨立的元素創建一個jQuery對象
                var $this = $(this);


// 執行代碼

// 例如: privateFunction();
            });
        },
        destroy: function() {

// 對選擇器每個元素都執行方法
            return this.each(function() {

// 執行代碼
            });
        }
    };

    $.fn.pluginName = function() {

// 獲取我們的方法,遺憾的是,如果我們用function(method){}來實現,這樣會毀掉一切的
        var method = arguments[0];


// 檢驗方法是否存在
        if(methods[method]) {


// 如果方法存在,存儲起來以便使用

// 註意:我這樣做是為了等下更方便地使用each()
            method = methods[method];


// 如果方法不存在,檢驗對象是否為一個對象(JSON對象)或者method方法沒有被傳入
        } else if( typeof(method) == 'object' || !method ) {


// 如果我們傳入的是一個對象參數,或者根本沒有參數,init方法會被調用
            method = methods.init;
        } else {


// 如果方法不存在或者參數沒傳入,則報出錯誤。需要調用的方法沒有被正確調用
            $.error( 'Method ' +  method + ' does not exist on jQuery.pluginName' );
            return this;
        }


// 調用我們選中的方法

// 再一次註意我們是如何將each()從這裡轉移到每個單獨的方法上的
        return method.call(this);

    }

})(jQuery);

註意我把 privateFunction 當做了一個函數內部的全局變數。考慮到所有的代碼的運行都是在插件容器內進行的,所以這種做法是可以被接受的,因為它只在插件的作用域中可用。在插件中的主函數中,我檢驗了傳入參數所指向的方法是否存在。如果方法不存在或者傳入的是參數為對象, init 方法會被運行。最後,如果傳入的參數不是一個對象而是一個不存在的方法,我們會報出一個錯誤信息。

同樣要註意的是,我是如何在每個方法中都使用this.each() 的。當我們在主函數中調用 method.call(this) 的時候,這裡的 this 事實上就是一個jQuery對象,作為 this 傳入每個方法中。所以在我們方法的即時作用域中,它已經是一個jQuery對象。只有在被 each()所調用的函數中,我們才有必要將this包裝在一個jQuery對象中。

下麵是一些用法的例子:

/*

註意這些例子可以在目前的插件代碼中正確運行,並不是所有的插件都使用同樣的代碼結構
*/
// 為每個類名為 ".className" 的元素執行init方法
$('.className').pluginName();
$('.className').pluginName('init');
$('.className').pluginName('init', {});
// 向init方法傳入“{}”對象作為函數參數
$('.className').pluginName({});
// 向init方法傳入“{}”對象作為函數參數

// 為每個類名為 “.className” 的元素執行destroy方法
$('.className').pluginName('destroy');
$('.className').pluginName('destroy', {});
// 向destroy方法傳入“{}”對象作為函數參數

// 所有代碼都可以正常運行
$('.className').pluginName('init', 'argument1', 'argument2');
// 把 "argument 1" 和 "argument 2" 傳入 "init"

// 不正確的使用
$('.className').pluginName('nonexistantMethod');
$('.className').pluginName('nonexistantMethod', {});
$('.className').pluginName('argument 1');
// 會嘗試調用 "argument 1" 方法
$('.className').pluginName('argument 1', 'argument 2');
// 會嘗試調用 "argument 1" ,“argument 2”方法
$('.className').pluginName('privateFunction');
// 'privateFunction' 不是一個方法

在上面的例子中多次出現了 {} ,表示的是傳入方法中的參數。在這小節中,上面代碼可以可以正常運行,但是參數不會被傳入方法中。繼續閱讀下一小節,你會知道如何向方法傳入參數。

設置插件:傳入參數

許多插件都支持參數傳入,如配置參數和回調函數。你可以通過傳入JS鍵值對對象或者函數參數,為方法提供信息。如果你的方法支持多於一個或兩個參數,那麼沒有比傳入對象參數更恰當的方式。

(function($) {
    var methods = {
        init: function(options) {


// 在每個元素上執行方法
            return this.each(function() {
                var $this = $(this);


// 創建一個預設設置對象
                var defaults = {
                    propertyName: 'value',
                    onSomeEvent: function() {}
                }


// 使用extend方法從options和defaults對象中構造出一個settings對象
                var settings = $.extend({}, defaults, options);


// 執行代碼

            });
        }
    };

    $.fn.pluginName = function() {
        var method = arguments[0];

        if(methods[method]) {
            method = methods[method];


// 我們的方法是作為參數傳入的,把它從參數列表中刪除,因為調用方法時並不需要它
            arguments = Array.prototype.slice.call(arguments, 1);
        } else if( typeof(method) == 'object' || !method ) {
            method = methods.init;
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.pluginName' );
            return this;
        }


// 用apply方法來調用我們的方法並傳入參數
        return method.apply(this, arguments);

    }

})(jQuery);

正如上面所示,一個“options”參數被添加到方法當中,和“arguments”也被添加到了主函數中。如果一個方法已經被聲明,在參數傳入方法之前,調用那個方法的參數會從參數列表中刪除掉。我用了“apply()”來代替了“call()”,“apply()”本質上是和“call()”做著同樣的工作的,但不同的是它允許參數的傳入。這種結構也允許多個參數的傳入,如果你願意這樣做,你也可以為你的方法修改參數列表,例如:“init:function(arg1, arg2){}”。

如果你是使用JS對象作為參數傳入,你可能需要定義一個預設對象。一旦預設對象被聲明,你可以使用“$.extend”來合併參數對象和預設對象中的值,以形成一個新的參數對象來使用(在我們的例子中就是“settings”);

這裡有一些例子,用來演示以上的邏輯:

var options = {
    customParameter: 'Test 1',
    propertyName: 'Test 2'
}

var defaults = {
    propertyName: 'Test 3',
    onSomeEvent: 'Test 4'
}

var settings = $.extend({}, defaults, options);
/*
settings == {

propertyName: 'Test 2',

onSomeEvent: 'Test 4',

customParameter: 'Test 1'
}
*/

保存設置:添加持久性數據

有時你會想在你的插件中保存設置和信息,這時jQuery中的“data()”函數就可以派上用場了。它在使用上是非常簡單的,它會嘗試獲取和元素相關的數據,如果數據不存在,它就會創造相應的數據並添加到元素上。一旦你使用了“data()”來為元素添加信息,請確認你已經記住,當不再需要數據的時候,用“removeData()”來刪除相應的數據。

// Shawn Khameneh
// ExtraordinaryThoughts.com

(function($) {
    var privateFunction = function() {

// 執行代碼
    }

    var methods = {
        init: function(options) {


// 在每個元素上執行方法
            return this.each(function() {
                var $this = $(this);


// 嘗試去獲取settings,如果不存在,則返回“undefined”
                var settings = $this.data('pluginName');


// 如果獲取settings失敗,則根據options和default創建它
                if(typeof(settings) == 'undefined') {

                    var defaults = {
                        propertyName: 'value',
                        onSomeEvent: function() {}
                    }

                    settings = $.extend({}, defaults, options);


// 保存我們新創建的settings
                    $this.data('pluginName', settings);
                } else {
                    / 如果我們獲取了settings,則將它和options進行合併(這不是必須的,你可以選擇不這樣做)
                    settings = $.extend({}, settings, options);


// 如果你想每次都保存options,可以添加下麵代碼:

// $this.data('pluginName', settings);
                }


// 執行代碼

            });
        },
        destroy: function(options) {

// 在每個元素中執行代碼
            return $(this).each(function() {
                var $this = $(this);


// 執行代碼


// 刪除元素對應的數據
                $this.removeData('pluginName');
            });
        },
        val: function(options) {

// 這裡的代碼通過.eq(0)來獲取選擇器中的第一個元素的,我們或獲取它的HTML內容作為我們的返回值
            var someValue = this.eq(0).html();


// 返回值
            return someValue;
        }
    };

    $.fn.pluginName = function() {
        var method = arguments[0];

        if(methods[method]) {
            method = methods[method];
            arguments = Array.prototype.slice.call(arguments, 1);
        } else if( typeof(method) == 'object' || !method ) {
            method = methods.init;
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.pluginName' );
            return this;
        }

        return method.apply(this, arguments);

    }

})(jQuery);

在上面的代碼中,我檢驗了元素的數據是否存在。如果數據不存在,“options”和“default”會被合併,構建成一個新的settings,然後用“data()”保存在元素中。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • "你從哪裡來?” “你要到哪裡去?" 這是保全小哥經常會問的具有哲理性的問題。在互聯網的應用的開發中,也經常會用到有關地址的選擇設置。不管是物流的應用,還是外賣的應用,都會要求用戶設置用戶所在的位置。如果讓用戶來輸入完整的地址,一方面,輸入比較慢,體驗不好。另一方面,輸入的地址不規範,例如:"浙江省 ...
  • 本文章向碼農介紹css3選擇器的使用方法和總結,包括通用選擇器,屬性選擇器,偽類選擇器,對css選擇器相關知識感興趣的碼農可以閱讀一下本文章。 一 通用選擇器 1 *{}通配選擇符(CSS2):適合所有元素對象。2 E類型(HTML)選擇符(CSS1):以文檔語言對象類型DOM作為選擇符。3 E#m ...
  • 原文地址:http://www.neoease.com/css-z-index-property-and-layering-tree/ CSS 中的 z-index 屬性用於設置節點的堆疊順序, 擁有更高堆疊順序的節點將顯示在堆疊順序較低的節點前面, 這是我們對 z-index 屬性普遍的認識. 與 ...
  • 1.html代碼 <div id="qrcode" style="width:200px; height:200px;position: fixed;bottom: 40%; right: 20%;"></div> 2.引入外部js文件 <script src="QRCode.js"></scrip ...
  • Ruby(或cmd中)輸入命令行編譯sass步驟如下: (1)舉例而言:首先在F盤下建立一個總文件夾,比如test文件夾;其次在該文件夾下建立html,images,js,sass等文件夾。 (2)在sass文件夾中創建要使用到的sass文件。例如common.scss,reset.scss,con ...
  • 用js模擬jQuery方法,體會封裝思想 <!DOCTYPE html><html><head><meta charset="UTF-8"><title>simulationJquery.html</title> </head><body> <img src=images/123.jpg/> <in ...
  • background-size的基本屬性 background-size: 可以設定背景圖像的尺寸,該屬性是css3中的,在移動端使用的地方很多,比如最常見的地方在做響應性佈局的時候,比如之前做的項目中有輪播圖片,為了自適應不同大小解析度的圖片,我門需要使用css3中的媒體查詢來針對不同的解析度設置 ...
  • JavaScript是一個絕冠全球的編程語言,可用於Web開發、移動應用開發(PhoneGap、Appcelerator)、伺服器端開發(Node.js和Wakanda)等等。JavaScript還是很多新手踏入編程世界的第一個語言。既可以用來顯示瀏覽器中的簡單提示框,也可以通過nodebot或no ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...