当构建大型的JavaScript(尤其是那些有几十甚至几百个JavaScript文件)应用程序时,
使用类似Java的包结构来管理JavaScript是非常合适和有意义的,而且Java的包结构为在
JavaScript中引入名称空间准备了很好的模型。如果配合动态加载技术可以写出既优雅又很好
维护的代码,而不用担心或忘记引入一大推不知何用的JavaScript的文件。
比如,在一个大型项目中,所有的JavaScript文件(第三方的除外)可以这样组织:
../js ;
JavaScript的文件目录 ../js/com/
../js/com/jinfonet/
../js/com/jinfonet/util/ ;
一些工具类 ../js/com/jinfonet/... ;
其它使用这种方式来组织管理JavaScript文件,Java程序员大都会觉得理所当然,不过这也意味
着在使用这些JavaScript文件时,引入这些文件也是一件枯燥和容易忘记的工作,比如要维护
一大堆下面这种语句,
当增加新的JavaScript文件,或者减少一些,又或者重构很多JavaScript文件,维护这样的
东西是常令人沮丧的。
最近,看了jQuery动态加载JavaScript的实现,和一些在JavaScript里支持名称空间
的实现,启发自己实现了一个让Java程序员更习惯的JavaScript对象文件的组织管理机制。
这个实现目标是:
1) 一个类,一个文件。
2) 在每一个类里,定义自己的包名,并引入自己需要的其它类。实现类似在Java中定义
一个类的语法:
package com.jinfonet.util;
import java.util.List;
public class MyClass {
// ...
}
3) 除了个别用于初始化的,或者程序入口的JavaScript文件,其它相关的JavaScript
按需动态加载。
4) JavaScript文件按Java方式存储在包名目录下。
首先,创建基础的Clazz(../js/com/jinfonet/clazz.js)对象,这个Clazz属于名称空间window.com.jinfonet。见如下代码:
/**
* File: clazz.js
* Create: 2010-11-17
* Author: hoodng@hotmail.com
*/
com = window.com || {};
com.jinfonet = window.com.jinfonet || {};
com.jinfonet.Clazz = (function(){
return {
PATH_PREFIX : "../js",
/**
* Create a Namespace with specified package name
*
* @param packageName
* the package name shoule be like "com.jinfonet.util"
*/
namespace : function (packageName){
var names = packageName.split(".");
var parent = window;
for(var i = 0, len = names.length; i < len; i++){
var name = names[i];
if(parent[name] === undefined){
parent[name] = {};
}
parent = parent[name];
}
},
/**
* Return whether the specified class was defined
*
* @param clazzName
* The class name should be like "com.jinfonet.util.StringBuffer"
*/
defined : function(clazzName){
var names = clazzName.split(".");
var parent = window;
for ( var i = 0, len = names.length; i < len; i++) {
var name = names[i];
if (parent[name] === undefined){
return false;
}
parent = parent[name];
}
return parent !== undefined;
},
/**
* Imports a class dnamically
*
* @param className The class name that will import.
*/
imports : function(clazzName){
if (j$.defined(clazzName)) return;
var buf = new Array();
buf.push(j$.PATH_PREFIX);
var names = clazzName.toLowerCase().split(".");
for ( var i = 0, len = names.length; i < len; i++) {
buf.push("/");
buf.push(names[i]);
}
buf.push(".js");
var url = buf.join("");
var http = window.ActiveXObject ?
new ActiveXObject("Msxml2.XmlHttp") : new XMLHttpRequest();
http.open("GET", url, false);
http.send(null);
if (http.readyState == 4 &&
(http.status == 200 || http.status == 304)) {
var script = document.createElement("script");
script.type = "text/javascript";
script.text = http.responseText;
var head = document.getElementsByTagName("head")[0] ||
document.documentElement;
head.insertBefore(script, head.firstChild);
head.removeChild(script);
script = null;
}
}
};// End return
})();
j$ = window.com.jinfonet.Clazz;
|
以上是Clazz的源代码,有三个方法,
namespace(packageName) // 定义一个名称空间
defined(className) // 测试一个对象是否已经存在,用来标志一个JavaScript是否已经加载
imports(className) // 用来从文件加载一个JavaScript的对象
为了方便使用,定义短名称
j$指向window.com.jinfonet.Clazz。通常,我们只需要用到以下方法
1) 定义名称空间
j$.
namespace("com.jinfonet.utils");
2) 引入类
j$.
imports("com.jinfonet.utils.StringBuffer");
到此,已经具备定义名称空间,并动态导入JavaScript的能力。比如,我们现在要创建一个com.jinfonet.utils.StringBuffer类,那么,我们需要在路径../js/com/jinfonet/utils/下创建JavaScript文件stringbuffer.js, 代码如下:
/**
* File: stringbuffer.js
* Create: 2010-11-17
* Author: hoodng@hotmail.com
*/
//定义名称空间,是不是很像Java的package com.jinfonet.utils;
j$.namespace("com.jinfonet.utils");
com.jinfonet.utils.StringBuffer = function StringBuffer() {
this.__buf__ = new Array();
};
with(com.jinfonet.utils){
StringBuffer.prototype.append = function(str){
this.__buf__.push(str);
return this;
};
StringBuffer.prototype.toString = function(){
return this.__buf__.join('');
};
};
|
以上是我们使用名称空间创建了第一个工具类StringBuffer,还没有看出来优雅在何处。我们再定义另外一个工具类,com.jinfonet.utils.Logger,用于在一个弹出的窗口中显示一些message,可以替代alert。
同样,这个Logger应该对应物理文件../js/com/jinfonet/utils/logger.js,代码如下:
/**
* File: logger.js
* Create: 2010-11-17
* Author: hoodng@hotmail.com
*/
//定义名称空间,是不是很像Java的package com.jinfonet.utils;
j$.namespace("com.jinfonet.utils");
//不优雅吗,这个类用到StringBuffer所以在这里导入,这是很自然的,
//是不是很像Java的import com.jinfonet.utils.StringBuffer;
j$.imports("com.jinfonet.utils.StringBuffer");
com.jinfonet.utils.Logger = (function (){
var __log__ = undefined;
j$.initLog = function(){
com.jinfonet.utils.Logger.init();
};
j$.log = function(msg){
com.jinfonet.utils.Logger.log(msg);
};
return {
init : function(){
var win = window.open("", "LogWindow",
"height=400, width=200, top=0, left=0, ntoolbar=no, menubar=no, " +
"scrollbars=yes, resizable=yes, location=no, status=no");
var doc = win.document;
doc.close();
doc.open();
doc.write("Log window...");
doc.write("
+ "style='width:100%; height:100%; white-space:nowrap;'>");
doc.write("
");
this.__log__ = doc.getElementById("__log__");
window.alert = function(msg){
j$.log(msg);
};
},
log : function(msg){
var now = (new Date()).toString();
var buf = new com.jinfonet.utils.StringBuffer();
if(this.__log__){
buf.append("");
buf.append("" ).append(now).append("");
buf.append("" );
buf.append(msg).append("");
this.__log__.innerHTML += buf.toString();
}else{
buf.append(now).append(":\n").append(msg);
alert(buf.toString());
}
buf = null;
}
}; // End return
})();
|
到这里,我们还没有开始真正的应用程序,所以还没有体现出动态加载的优雅,我们接着再来,写一个简单的应用程序,代码如下:
<html>
<head>
<script type="text/javascript" src="../js/com/jinfonet/clazz.js"></script>
<script type="text/javascript" src="../js/com/jinfonet/utils/logger.js"></script>
<script type="text/javascript">
j$.initLog();
var i = 0;
var n = 10;
while(1){
//j$.log(""+i);
alert(""+i);
i++;
if (i >= n ) break ;
}
j$.log("end");
</script>
</head>
<body>
JavaScript test
</body>
</html>
|
注意看,在这个应用中,我们没有显示的导入StringBuffer,但StringBuffer在导入logger.js时已经被动态加载了。
理论上,我们只需要显示导入clazz.js和主应用入口的JavaScript文件,其它的都是动态加载的,由每一个JavaScript文件的作者确定她需要导入哪些类,这样就避免了先前说的,在大型工程中要维护一大堆不知所谓的JavaScript导入。
阅读(2349) | 评论(1) | 转发(0) |