標籤:
前言:
現在javascript庫特別多,其寫法各式各樣,總結幾種我們經常見到的,作為自己知識的積累。而目前版本的 JavaScript 並未提供一種原生的、語言層級的模組化組織模式,而是將模組化的方法交由開發人員來實現。因此,出現了很多種 JavaScript 模組化的實現方式,
以 AMD 為例,該規範使用 define 函數來定義模組。
define(factory(){ //模組化});
模組模式:
模組模式使用了 JavaScript 的一個特性,即閉包(Closures)。現今流行的一些 JS 庫中經常見到以下形式的代碼:
;(function (參數) { // 模組代碼 // return something;})(參數);
上面的代碼定義了一個匿名函數,並立即調用自己。
也有一些開發人員在函數運算式前面加上一個驚歎號(!)或分號(;),而不是用括弧包起來。
!function (參數) { // 代碼 // return something}(參數);
還有些人喜歡用括弧將整個 IIFE 圍起來,這樣就變成了以下的形式:
(function (參數) { // 代碼 // return something}(參數));參數輸入:
JavaScript 有一個特性叫做隱式全域變數(implied globals),當使用一個變數名時,JavaScript 解譯器將反向遍曆範圍鏈來尋找變數的聲明,如果沒有找到,就假定該變數是全域變數。這種特性使得我們可以在閉包裡隨處引用全域變數,比如 jQuery 或 window。然而,這是一種不好的方式。
考慮模組的獨立性和封裝,對其它對象的引用應該通過參數來引入。如果模組內需要使用其它全域對象,應該將這些對象作為參數來顯式引用它們,而非在模組內直 接引用這些對象的名字。以 jQuery 為例,若在參數中沒有輸入 jQuery 對象就在模組內直接引用 $ 這個對象,是有出錯的可能的。正確的方式大致應該是這樣的:
;(function ($, w) { // $ is jQuery // w is window // 局部變數及代碼 // 返回})(jQuery, window);模組輸出(Module Export)
有時我們不只是要使用全域變數,我們也要聲明和輸出模組中的對象,這可以通過匿名函數的 return 語句來達成,而這也構成了一個完整的模組模式。
var klm= (function () { var myklm = {}, modeval= 1; function privateMethod() { // ... } myklm.moduleProperty = 1; mymyklm.moduleMethod = function () { // ... }; return myklm ;}());
這段代碼聲明了一個變數 MODULE,它帶有兩個可訪問的屬性:moduleProperty 和 moduleMethod,其它的代碼都封裝在閉包中保持著私人狀態。參考以前提過的參數輸入,我們還可以通過參數引用其它全域變數。
輸出簡單對象:
使用對象直接量來表達 JavaScript 對象是很常見的。比如:var x = { p1: 1, p2: "2", f: function(){ /*... */ } }
var Module1 = (function () { var private_variable = 1; function private_method() { /*...*/ } var my = { property1: 1, property2: private_variable, method1: private_method, method2: function () { // ... } }; return my;}());輸出函數:
有時候我們希望返回的並不是一個對象,而是一個函數。有兩種需求要求我們返回一個函數,一種情況是我們需要它是一個函數,比如 jQuery,它是一個函數而不是一個簡單對象;另一種情況是我們需要的是一個“類”而不是一個直接量,之後我們可以用 "new" 來執行個體它。目前版本的 JavaScript 並沒有專門的“類”定義,但它卻可以通過 function 來表達。
var klm= (function () { // 私人成員及代碼 ... return function(name) { this.name = name; this.bark = function() { /*...*/ } };}()); var com = new klm("蒯靈敏");com.bark();
前面已經提到一種形式是輸出對象直接量(Object Literal Notation),而 Revealing Module Pattern 其實就是這種形式,只是做了一些限定。這種模式要求在私人範圍內中定義變數和函數,然後返回一個匿名對象,在該對象中指定要公開的成員。
var klm= (function () { // 私人變數及函數 var x = 1; function f1() {} function f2() {} return { public_method1: f1, public_method2: f2 };}());擴充:緊耦合擴充:
有時我們要求在擴充時調用以前已被定義的方法,這也有可能被用於覆蓋已有的方法。這時,對模組的定義順序是有要求的。
var klm = (function (my) { var old_moduleMethod = my.moduleMethod; my.moduleMethod = function () { // 方法重載 // 可通過 old_moduleMethod 調用以前的方法... }; return my;}(klm));複製與繼承:
var MODULE_TWO = (function (old) { var my = {}, key; for (key in old) { if (old.hasOwnProperty(key)) { my[key] = old[key]; } } var super_moduleMethod = old.moduleMethod; my.moduleMethod = function () { // override method on the clone, access to super through super_moduleMethod }; return my;}(MODULE));
上面代碼再精簡一下:可以使用 Object.create()
var MODULE_TWO = (function (old) { var my = Object.create(old); var super_moduleMethod = old.moduleMethod; my.moduleMethod = function () { // override method ... }; return my;}(MODULE)); 與其它模組規範或 JS 庫的適配:模組環境探測:
現今,CommonJS Modules 與 AMD 有著廣泛的應用,如果確定 AMD 的 define 是可用的,我們當然可以使用 define 來編寫模組化的代碼。然而,我們不能假定我們的代碼必然運行於 AMD 環境下,有沒有辦法可以讓我們的代碼?
其實我們只需要在某個地方加上對 CommonJS Modules 與 AMD 的探測並根據探測結果來“註冊”自己就可以了,以上那些模組模式仍然有用。AMD 定義了 define 函數,我們可以使用 typeof 探測該函數是否已定義。若要更嚴格一點,可以繼續判斷 define.amd 是否有定義。另外,SeaJS 也使用了 define 函數,但和 AMD 的 define 又不太一樣。對於 CommonJS,可以檢查 exports 或是 module.exports 是否有定義。
var klm = (function () { var my = {}; // 代碼 ... if (typeof define == ‘function‘) { define( function(){ return my; } ); }else if (typeof module != ‘undefined‘ && module.exports) { module.exports = my; } return my;}());其它一些 JS 庫的做法
jquery的檢測方式
if ( typeof module === "object" && module && typeof module.exports === "object" ) { module.exports = jQuery;} else { if ( typeof define === "function" && define.amd ) { define( "jquery", [], function () { return jQuery; } ); }} if ( typeof window === "object" && typeof window.document === "object" ) { window.jQuery = window.$ = jQuery;}
接下來看看多重匿名函數的做法:
(function (root, factory) { if (typeof exports === "object" && exports) { factory(exports); // CommonJS } else { var mustache = {}; factory(mustache); if (typeof define === "function" && define.amd) { define(mustache); // AMD } else { root.Mustache = mustache; // <script> } }}(this, function (mustache) { // 模組主要的代碼放在這兒});
這段代碼與前面介紹的方式不太一樣,它使用了兩個匿名函數。後面那個函數可以看作是模組代碼的工廠函數,它是模組的主體部分。前面那個函數對運行環境進行 檢測,根據檢測的結果對模組的工廠函數進行調用。另外,作為一個通用庫,它並沒使用 window 對象,而是使用了 this,因為在簡單的函數調用中,this 其實就是全域對象。
再看看 doT 的做法
(function() { "use strict"; var doT = { version: ‘1.0.0‘, templateSettings: { /*...*/ }, template: undefined, //fn, compile template compile: undefined //fn, for express }; if (typeof module !== ‘undefined‘ && module.exports) { module.exports = doT; } else if (typeof define === ‘function‘ && define.amd) { define(function(){return doT;}); } else { (function(){ return this || (0,eval)(‘this‘); }()).doT = doT; } // ...}());
這段代碼裡的 (0, eval)(‘this‘) 是一個小技巧,這個運算式用來得到 Global 對象,‘this‘ 其實是傳遞給 eval 的參數,但由於 eval 是經由 (0, eval) 這個運算式間接得到的,因此 eval 將會在全域對象範圍中尋找 this,結果得到的是全域對象。若是代碼運行於瀏覽器中,那麼得到的其實是 window 對象。
JavaScript 模組化的未來
尚在制定中的 ES 6 會對模組作出語言層級的定義。我們來看一個執行個體,以下的程式碼片段摘自“ES6:JavaScript中將會有的幾個新東西”
module Car { // 內部變數 var licensePlateNo = ‘556-343‘; // 暴露到外部的變數和函數 export function drive(speed, direction) { console.log(‘details:‘, speed, direction); } export module engine{ export function check() { } } export var miles = 5000; export var color = ‘silver‘; };
在以後自己寫js封裝的時候上面是我參考的資料,繼續激情的前進著。。。
js庫寫法