沙盘模式可以弥补命名空间模式中的两项不足之处:

  • 使用唯一全局对象作为程序的全局变量入口,使得无法在同一程序中使用两个不同版本的API,因此它们使用的是同一个唯一的全局对象名,如MYAPP;
  • 较长的嵌套对象使得代码编写和解析都比较慢;
沙盘模式,正如其名字所说,提供了一个各类模块可以共同“游戏”的环境,模块之间和沙盘之间不会相互影响。
这种模式在YUI3中大量被使用。下面介绍一种沙盘模式的实现方式:
1. 全局的构造函数
在命名空间模式中只有一个全局对象;在沙盘模式中,唯一的是一个全局的构造函数:比如说,Sandbox()。Sandbox只是本例使用的名字,程序里应该使用一个有具体含义的名字。这个全局的构造函数接受一个回调函数,并为这个回调函数提供沙盘环境。
沙盘构造函数的调用大概是这样的:
new Sandbox(function (box) {
// your code here...
});
对象box就好像命名空间模式中的MYAPP,提供了程序库当中需要的功能。
然后再向沙盘模式加入两样技术:
使用之前提到的检查方法,保证总是用new来调用沙盘构造函数;
沙盘构造函数接受参数,来告知沙盘内应提供哪些模块的功能;
有了这两个技术,我们会检查出Sandbox()不用new来调用,并在Sandbox()内部再用new来调用它自己:保证它总是用new来调用的;并传入适当的参数来通知沙盘提供相应的功能。这样,Sanbox()的调用过程就变成了这样:
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()创建的对象相互独立并依赖于不同的功能模块。另外,由于Sandbox是函数,因此它也是对象,所以还可以向Sandbox()加入静态属性来存放一些数据。
2. 定义模块
在 介绍Sandbox的实现之前,先看看如何定义模块。在沙盘模式中,全局的构造函数Sandbox是个函数对象,因此可以为它加入一个静态的属性来保存各 个模块,比如modules。modules是Sandbox的成员属性,也是一个对象,它通过内部的键值对来保存模块,键作为模块名,而值是模块的实 现。定义模块的做法是这样的:
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 () {};
};
上面的例子里,我们给modules对象加入了三个模块,分别是dom,event,以及ajax。
3. 实现Sandbox的沙盘构造函数
一个可行的Sandbox构造函数是这样的(实际上,在你的程序里可能需要把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的实例,如果不是(说明不是用new调用的Sandbox())就再次用new调用Sandbox;
  • 可以用this为构造函数添加属性,也可以通过prototype来添加属性;
  • 需要使用的模块可以通过模块名的数组传入,也可以使用每个参数一个模块名的方式传入,“*”匹配所有支持的模块(实际的使用中可以还需要引入定义所需模块的js文件);
  • 如果所需的模块已经定义了,就初始化它,也就是执行定义模块的函数;
  • Sandbox函数的最后一个参数是个回调函数,它在Sandbox函数的最后被调用,并使用Sandbox创建的对象来执行相应的代码,这些代码就像在一个沙盘中运行一样:它们得到了需要支持的模块,而且与外部代码相隔离。
05-19 06:31