Ajax-JavaScript-namespace
引入Namespace的目的
JavaScript中引入Namespace和C#一样,都是为了避免命名冲突。但是Js又有些特殊:
js解释执行,如果后面定义了同名的变量会覆盖前面变量,并用应用新定义变量的语义。这在Js中合法,没有错误提示,因此bug很难找。
可以想象,我调用了一个第三方模块,如果这个模块没有定义在Namespace中,我定义的变量和函数很可能就覆盖了第三方的同名符号。
模块原则
a module should never add more than a single symbol to the global namespace。
只有这样才能保证模块的重用性,MicrosoftAJax就是这样实现的。除了几个以$开头的全局符号,其余符号都定义在Sys命名空间中。
$符号
MA.js,部分违反了上面的原则,但是它对违反原则的变量命名都以$开头。这样也对模块的调用者有个约定,即:$开头的变量属于全局名称空间,调用者如果也以$命名变量必须自觉保证没有冲突。可以看到Animaions.js有类似用法。
类似的约定还有:以_开头的变量为私有,不应该调用。他们的实现可能会变化,调用者如果应用,必须做层封装。
Namespace通常实现
对于JS这种动态语言来说,实现namespace很简单,定义一个对象变量即可。
如:
Sys.Debug={trace:'trace',fail:'fail'};
Sys.StringBuilder={};
document.write(Sys.Debug.fail);
定义了Sys名称空间,在其中定义了Debug,StringBuilder对象。
上面的定义过于简单,实际上还要做一下检测,看看是否已经定义过相同的名称空间,如果有跑出异常或者用已经存在的名称空间;是否namespace是对象,不是也抛异常。
if(!Sys) Sys={};
else if(typeof Sys!=object)
throw new Error('类型错误');
Sys.Debug={trace:'trace',fail:'fail'};
Sys.StringBuilder={};
document.write(Sys.Debug.fail)
上面是比较严格的实现,MicrosoftAjax.js,就是用上面的逻辑,只是他代码更复杂些。
注:在Antechinus中如果不指定undefined,会出错。但是Js语法没有这种要求。猜测Antechinus可能会先读取Sys,读没有声明的变量会有异常。
MicrosoftAjax.js实现
MA中注册Namespace,通过循环的方式,window对象保持对rootNamespace的引用,rootNamespace又引用子空间。形成一条链。
如注册:Type.registerNamespace('My.I.M');
会注册下面三个空间:My,My.I,My.I.M。其中My是rootNamespace。My,My.I被隐含注册,可以直接使用。
MA的实现看起来很复杂,但是只要注意下面几条还是很容易看懂的:
利用window
var rootObject = window;
在第一次循环中rootObject一直指向window,如果根没有被注册则在window中注册根命名空间。结果是:window[‘My’]={},window===rootObject。
第二次循环,winodw指向rootObject[‘My’]。以My为根,注册I。结果:window[‘My’][‘I’]={},window===rootObject[‘My’]。
第三次注册My.I.M。
因为用到了window,RegisterNamespace也失去了通用性。
Eval
值支持primitive value
code
A string that contains the JavaScript expression to be evaluated or the statements to be executed.
The value of the evaluated code, if any.
类似数组的语法访问对象
如winodw[‘Sys’][‘Net’]
代码:
/// <param name="namespacePath" type="String"></param>
var e = Function._validateParams(arguments, [
{name: "namespacePath", type: String}
]);
if (e) throw e;
if (!Type.__fullyQualifiedIdentifierRegExp.test(namespacePath)) throw Error.argument('namespacePath', Sys.Res.invalidNameSpace);
var rootObject = window;
var namespaceParts = namespacePath.split('.');
for (var i = 0; i < namespaceParts.length; i++) {
var currentPart = namespaceParts[i];
var ns = rootObject[currentPart];
if (ns && !ns.__namespace) {
throw Error.invalidOperation(String.format(Sys.Res.namespaceContainsObject, namespaceParts.splice(0, i + 1).join('.')));
}
if (!ns) {
ns = rootObject[currentPart] = {};
if (i === 0) {
window.__rootNamespaces[window.__rootNamespaces.length] = ns;
}
ns.__namespace = true;
ns.__typeName = namespaceParts.slice(0, i + 1).join('.');
var parsedName;
try {
parsedName = eval(ns.__typeName);
}
catch(e) {
parsedName = null;
}
if (parsedName !== ns) {
delete rootObject[currentPart];
throw Error.argument('namespacePath', Sys.Res.invalidNameSpace);
}
ns.getName = function ns$getName() {return this.__typeName;}
}
rootObject = ns;
}
}