沙箱模式(Sandbox Pattern)沙箱模式可以避免命名空間的一些缺點(namespacing pattern),比如:
- 依賴一個唯一全域的變數作為程式的全域符號。在命名空間模式中,沒有辦法存在兩個版本程式或者類庫在相同的頁面中運行,因為它們都需要相同的全域符號,比如:MYAPP
- 長的帶點的名稱去輸入和運行時解析,比如:MYAPP.utilities.array
顧名思義,沙箱模式給模組提供一個環境運行而不影響其它模組和它們私人的沙箱。這個模式在 YUI version 3被廣泛使用,但記住接下來的討論是一個簡單的參考實現而不是試圖討論 YUI3的沙箱是怎麼實現的。
一個全域建構函式(A Global Constructor) 在命名空間模式中,你有一個全域對象;在沙箱模式中,這個單一的全域對象是一個構造方法:我們就叫它:Sandbox(). 你可以使用這個建構函式建立對象,你也可以傳遞一個回呼函數(callback function),這個函數將會成為你的代碼的獨立的沙箱環境。 使用沙箱模式看起來像:
new Sandbox(function (box) { // your code here...});
對象box和命名空間模式裡面的MYAPP比較像,它將會擁有讓你代碼運行需要的所有類庫功能函數。
讓我們再增加兩個東西到這個模式:
- 一些技巧(強制new模式),你可以假設new並且在建立對象的時候不需要它
- 這個Sandbox() 建構函式可以接受一個額外的配置參數(configuration argument )指定這個對象執行個體建立所需要的模組名,我們希望代碼模組化,那麼絕大部分Sandbox()提供的函數將會包含在模組中
有了這兩個額外的功能,我們看一些執行個體化對象的例子代碼像什麼樣子。 你可以省略new並且建立一個對象,使用了虛構的ajax和event模組,像這樣:
Sandbox(['ajax', 'event'], function (box) { // console.log(box);});
這個例子和前面的類似,但這一次模組名是作為獨立的參數傳遞的:
Sandbox('ajax', 'dom', function (box) { // console.log(box);});
那麼使用萬用字元 * 參數表示"所有可用的模組"怎麼樣?方便起見,讓我們假設當沒有模組被傳遞,沙箱會假設 * .那麼有兩種方法使用所有可用的模組,像下面這樣:
Sandbox('*', function (box) { // console.log(box);});Sandbox(function (box) { // console.log(box);});
還有一個例子可以說明如何多次執行個體化沙箱對象,並且你甚至可以一個中嵌套在另一個而沒有幹擾:
Sandbox('dom', 'event', function (box) { // work with dom and event Sandbox('ajax', function (box) { // another sandboxed "box" object // this "box" is not the same as // the "box" outside this function //... // done with Ajax }); // no trace of Ajax module here});
就想你在這些例子中看到的,當使用沙箱模式時,你可以將你的程式碼封裝裹進回呼函數保護全域的命名空間。如果你需要,你也可以使用函數也是對象的事實,儲存一些資料作為Sandbox()建構函式的"靜態(static)"的屬性。
最後,你可以擁有依賴不同的模組的不同的執行個體並且這些執行個體互相獨立工作。
現在讓我們看一下如何著手實現Sandbox()建構函式。
添加模組(Adding Modules)
在實現真正的建構函式之前,讓我們看看如何添加模組。 Sandbox() 函數也是一個對象,我們可以給它添加一個叫做modules的屬性。這個屬性將會是另外一個包含索引值對的對象,鍵是模組的名字,值是每個模組的實現函數。
Sandbox.modules = {};Sandbox.modules.dom = function (box) { box.getElement = function () {}; box.getStyle = function () {}; box.foo = "bar";};Sandbox.modules.event = function (box) { // access to the Sandbox prototype if needed: // box.constructor.prototype.m = "mmm"; box.attachEvent = function () {}; box.dettachEvent = function () {};};Sandbox.modules.ajax = function (box) { box.makeRequest = function () {}; box.getResponse = function () {};};
在這個例子中,我們添加了模組dom,event和ajax,都是一些在任何類庫或複雜的web項目中常見的基礎功能函數。每個模組的實現函數都接收通用的box執行個體作為參數並且可能給這個執行個體添加額外的屬性或方法。
實現建構函式(Implementing the Constructor)
最後,讓我們來實現Sandbox()建構函式(通常你可能會重新命名這種類型的建構函式,起一個對你的類庫或者程式有意義的名字):
function Sandbox() { // turning arguments into an array var args = Array.prototype.slice.call(arguments), // the last argument is the callback callback = args.pop(), // modules can be passed as an array or as individual parameters modules = (args[0] && typeof args[0] === "string") ? args : args[0], i; // make sure the function is called // as a constructor if (!(this instanceof Sandbox)) { return new Sandbox(modules, callback); } // add properties to `this` as needed: this.a = 1; this.b = 2; // now add modules to the core `this` object // no modules or "*" both mean "use all modules" if (!modules || modules === '*') { modules = []; for (i in Sandbox.modules) { if (Sandbox.modules.hasOwnProperty(i)) { modules.push(i); } } } // initialize the required modules for (i = 0; i < modules.length; i += 1) { Sandbox.modules[modules[i]](this); } // call the callback callback(this);}// any prototype properties as neededSandbox.prototype = { name: "My Application", version: "1.0", getName: function () { return this.name; }};
在這個實現的重點:
- 有個檢查this是否是Sandbox的執行個體,如果不是(意味著Sandbox()不是使用new調用),我們將它作為建構函式再調用一次
- 你可以在建構函式中給this添加屬性,你也可以給建構函式的原型添加屬性
- 需要的模組可以用一個模組名數組傳遞,或者作為獨立的參數,或者是 * 萬用字元(或者省略),這意味著我們應該載入所有的可訪問的模組。注意在這個例子中,我們沒有擔心從其它檔案中載入需要的函數,但要瞭解有這個可能。這個在YUI3中就被支援。你可以只載入最基礎的模組,並且無論你需要什麼模組都可以從其它檔案中載入,通過命名規範——檔案名稱對應模組名
- 當我們知道需要的模組,我們可以初始化他們,意味著我們可以調用每個模組的實現函數
- 最後一個參數是回呼函數。這個回呼函數會在最後使用新建立執行個體作為參數被調用,這個回調實際上就是使用者的沙箱,並且它會讓box對象被所有要求的函數填充。