深入理解jQuery外掛開發總結(三)

BothEyes1993發表於2019-02-22

容器:一個即時執行函式

根本上來說,每個外掛的程式碼是被包含在一個即時執行的函式當中,如下:

(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()”儲存在元素中。

相關文章