JavaScript 設計模式 :安全沙箱模式
命名空間
JavaScript本身中沒有提供命名空間機制,所以為了避免不同函數、對象以及變量名對全局空間的污染,通常的做法是為你的應用程序或者庫創建一個唯一的全局對象,然后將所有方法與屬性添加到這個對象上。
2 // constructors
3 function Parent() {}
4 function Child() {}
5 // a variable
6 var some_var = 1;
7 // some objects
8 var module1 = {};
9 module1.data = {a: 1, b: 2};
10 var module2 = {};
11 /* AFTER: 1 global */
12 // global object
13 var MYAPP = {};
14 // constructors
15 MYAPP.Parent = function() {};
16 MYAPP.Child = function() {};
17 // a variable
18 MYAPP.some_var = 1;
19 // an object
20 MYAPP.modules = {};
21 // nested objects
22 MYAPP.modules.module1 = {};
23 MYAPP.modules.module1.data = {a: 1, b: 2};
24 MYAPP.modules.module2 = {};
在這段代碼中,你創建了一個全局對象MYAPP,并將其他所有對象、函數作為屬性附加到MYAPP上。通常這是一種較好的避免命名沖突的方法,它被應用在很多項目中,但這種方法有一些缺點。
1.需要給所有需要添加的函數、變量加上前綴。
2.因為只有一個全局對象,這意味著一部分代碼可以肆意地修改全局對象而導致其余代碼的被動更新。
全局構造器
你可以用一個全局構造器,而不是一個全局對象,我們給這個構造器起名為Sandbox(),你可以用這個構造器創建對象,你還可以為構造器傳遞一個回調函數作為參數,這個回調函數就是你存放代碼的獨立沙箱環境。
2 // your code here...
3 });
讓我們給沙箱添加點別的特性:
1.創建沙箱時可以不使用'new'操作符。
2.Sandbox()構造器接受一些額外的配置參數,這些參數定義了生成對象所需模塊的名稱,我們希望代碼更加模塊化。
擁有了以上特性后,讓我們看看怎樣初始化一個對象。以下代碼顯示了你可以在不需要‘new’操作符的情況下,創建一個調用了'ajax'和'event'模塊的對象。
2 // console.log(box);
3 });
2 // console.log(box);
3 });
以下代碼顯示了你可以把通配符'*'作為參數傳遞給構造器,這意味著調用所有可用的模塊,為了方便起見,如果沒有向構造器傳遞任何模塊名作為參數,構造器會把'*'作為缺省參數傳入。
2 // console.log(box);
3 });
4 Sandbox(function(box){
5 // console.log(box);
6 });
以下代碼顯示你可以初始化沙箱對象多次,甚至你可以嵌套它們,而不用擔心彼此間會產生任何沖突。
2 // work with dom and event
3 Sandbox('ajax', function(box) {
4 // another sandboxed "box" object
5 // this "box" is not the same as
6 // the "box" outside this function
7 //...
8 // done with Ajax
9 });
10 // no trace of Ajax module here
11 });
從上面這些示例可以看出,使用沙箱模式,通過把所有代碼邏輯包裹在一個回調函數中,你根據所需模塊的不同生成不同的實例,而這些實例彼此互不干擾獨立的工作著,從而保護了全局命名空間。現在讓我們看看怎樣實現這個Sandbox()構造器。
添加模塊
在實現主構造器之前,讓我們看看如何向Sandbox()構造器中添加模塊。
因為Sandbox()構造器函數也是對象,所以你可以給它添加一個名為’modules'的屬性,這個屬性將是一個包含一組鍵值對的對象,其中每對鍵值對中Key是需要注冊的模塊名,而Value則是該模塊的入口函數,當構造器初始化時當前實例會作為第一個參數傳遞給入口函數,這樣入口函數就能為該實例添加額外的屬性與方法。
我們添加了'dom','event','ajax'模塊。
2 Sandbox.modules.dom = function(box) {
3 box.getElement = function() {};
4 box.getStyle = function() {};
5 box.foo = "bar";
6 };
7 Sandbox.modules.event = function(box) {
8 // access to the Sandbox prototype if needed:
9 // box.constructor.prototype.m = "mmm";
10 box.attachEvent = function(){};
11 box.dettachEvent = function(){};
12 };
13 Sandbox.modules.ajax = function(box) {
14 box.makeRequest = function() {};
15 box.getResponse = function() {};
16 };
實現構造器
描述了實現構造器的方法,其中關鍵的幾個要點:
1.我們檢查this是否為Sandbox的實例,若不是,證明Sandbox沒有被new操作符調用,我們將以構造器方式重新調用它。
2.你可以在構造器內部為this添加屬性,同樣你也可以為構造器的原型添加屬性。
3.模塊名稱會以數組、獨立參數、通配符‘*’等多種形式傳遞給構造器。
4.請注意在這個例子中我們不需要從外部文件中加載模塊,但在諸如YUI3中,你可以僅僅加載基礎模塊(通常被稱作種子(seed)),而其他的所有模塊則會從外部文件中加載。
5.一旦我們知道了所需的模塊,并初始化他們,這意味著調用了每個模塊的入口函數。
6.回調函數作為參數被最后傳入構造器,它將使用最新生成的實例并在最后執行。
2 // turning arguments into an array
3 var args = Array.prototype.slice.call(arguments),
4 // the last argument is the callback
5 callback = args.pop(),
6 // modules can be passed as an array or as individual parameters
7 modules = (args[0] && typeof args[0] === "string") ?
8 args : args[0],
9 i;
10 // make sure the function is called
11 // as a constructor
12 if (!(this instanceof Sandbox)) {
13 return new Sandbox(modules, callback);
14 }
15 // add properties to 'this' as needed:
16 this.a = 1;
17 this.b = 2;
18 // now add modules to the core 'this' object
19 // no modules or "*" both mean "use all modules"
20 if (!modules || modules === '*') {
21 modules = [];
22 for (i in Sandbox.modules) {
23 if (Sandbox.modules.hasOwnProperty(i)) {
24 modules.push(i);
25 }
26 }
27 }
28 // init the required modules
29 for (i = 0; i < modules.length; i++) {
30 Sandbox.modules[modules[i]](this);
31 }
32 // call the callback
33 callback(this);
34 }
35 // any prototype properties as needed
36 Sandbox.prototype = {
37 name: "My Application",
38 version: "1.0",
39 getName: function() {
40 return this.name;
41 }
42 };
原文來自:Stoyan Stefanov - JavaScript Patterns Part 7:The Sandbox Pattern