沙箱模式(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 needed
Sandbox.prototype = {
name: "My Application",
version: "1.0",
getName: function () {
return this.name;
}
};
在这个实现的重点:
- 有个检查this是否是Sandbox的实例,如果不是(意味着Sandbox()不是使用new调用),我们将它作为构造函数再调用一次
- 你可以在构造函数中给this添加属性,你也可以给构造函数的原型添加属性
- 需要的模块可以用一个模块名数组传递,或者作为独立的参数,或者是 * 通配符(或者省略),这意味着我们应该载入所有的可访问的模块。注意在这个例子中,我们没有担心从其它文件中载入需要的函数,但要了解有这个可能。这个在YUI3中就被支持。你可以只载入最基础的模块,并且无论你需要什么模块都可以从其它文件中载入,通过命名规范——文件名对应模块名
- 当我们知道需要的模块,我们可以初始化他们,意味着我们可以调用每个模块的实现函数
- 最后一个参数是回调函数。这个回调函数会在最后使用新创建实例作为参数被调用,这个回调实际上就是用户的沙箱,并且它会让box对象被所有要求的函数填充。