JSPackager使用指南
2.1 基于JSPackager的JS程序功能模块分类...
4.1 集成Prototype、Scriptaculous脚本实例
6.2 包描述对象($JSPackager.PackageInfo)常用方法
本项目旨在提供一种脚本库管理解决方案,让类库用户使用起来更加简洁方便,方便于组织/重用已有脚本;同时我们尽量做到简洁,不加入特殊语法。
我们作为一个开放的整合已有类库的框架,不打算提供过多的实用API,而是让最终用户根据自己喜好整合其他类库,当能我们也会提供一些整合已有著名脚本类库的实例。
具体功能有:
l 给普通javascript脚本加入包概念,通过包结构组织脚本。
l 提供一种导入/重用多方javascript脚本的解决方案。
l 统一管理脚本依赖,把程序员从冗余的依赖管理中解脱出来。
l 解决多方脚本的命名冲突问题,多版本共用问题。
l 解决重复加载问题,想用什么就导入什么,不用担心重复的导入语句。
更多更新信息请登录我们的网站:
http://www.opencode.cn/jspackager(国内)
http://jspackager.sourceforge.net(境外)
这里说的是核心框架代码的命名规则,不是最终用户的命名规则。
系统核心代码的命名需要考虑与用户的命名冲突的问题,即尽量减少与用户命名冲突的可能。
本系统中的全局变量/函数统一使用$开头。更多细节见附录:框架命名规范
通过<script>标记引入或直接编写的脚本,我们不建议在使用JSPackager之后,仍旧使用script src导入非JSPackager启动脚本之外的脚本。
通过$import函数直接或间接加载的脚本。这些脚本将在一个特殊的环境下加载,不会污染全局环境。
强依赖,脚本装载时即需要所依赖的东西,所以需要在当前脚本加载之前加载或声明依赖元素
弱依赖,只有真正调用这里面的方法时才需要加载依赖元素。
类库的使用者,您只需再页面上书写脚本,导入类库,编写自己的简单脚本,一般情况请不要编写js文件,那是类库开发者做的事.
在此框架下开发出方便实用的脚本库,您需要编写一些通用脚本,同时设置脚本依赖,做到任何类/函数,导入即可运行.
负责系统框架的设计,维护.
非也,JavaScript的历史可算悠久了,使用它编写的类库也不少,但是因为JavaScript自身的特点,难以组织管理,难以重用,这些零星的类库缺乏有效的管理而杂乱的堆砌在一起将会给以后的维护造成很大的麻烦。
JSPackager的出现,将有望结束这个恶梦,它将普通的JavaScript加上包的概念,通过包去组织脚本,而且,还提供了一个统一的依赖管理,将脚本间复杂的依赖关系有效的组织起来、封装起来。只要求类库开发者管理好自己类库的直接依赖即可(不需要管理间接依赖了),而类库的使用者更是只需导入使用即使,完全不必理会烦杂的依赖。
类似的装包工具也出现过,如dojo的装包系统,但是那是一个封闭的系统,不能重用已有的类库,而且有代码依赖。与之相比,JSPackager可以轻松加入已有的成熟的脚本库,而且代码完全可以脱离框架的依赖,包结构信息也只存在于独立的定义文件中。编写类库时,你甚至感觉不到包的存在。这还将有利于包的重构。
保持JavaScript的简单性这点我们非常重视,我们在设计框架的过程中也非常注重这点,虽然JSPackager框架本身逻辑略显繁琐,但是真正需要开发者掌握的API非常少,也没有新加什么特别的语法(导入指令其实就是一个普通的全局函数)。
不过JSPackager对于程序员的面向对象设计思想要求较高,如果你是一个初学者,可能有些东西不太好理解。
编写基于JSPackager的应用程序必需掌握的API只有:
$import函数:用于导入指定元素(类、函数、变量)
addScriptObject、addScriptDependence函数:都是PackageInfo的成员函数,用于定义包信息(包中每个脚本包含的圆熟)、脚本依赖。
使用JSPackager后,命名冲突的可能性将大大减少,应为我们的每段脚本都是有独立的加载环境。你甚至可以同时使用同一个类库的多个不同版本。
只要你没有定义双向的装载期依赖(这在任何装包系统中都不可能实现,逻辑上也时不成立的),就不会。
类库脚本:该部分可以是完全与核心框架无关的第三方脚本库,也可以是自己编写的类库,对该部分的脚本没有对框架的依赖要求。
包定义文件(__$package.js):用于描述脚本类库结构及其依赖关系的脚本。由类库开发者或集成者编写。
JSPackager引导脚本(核心框架):用于支撑装包系统工作的启动脚本。
网页脚本:具体页面的脚本,是最前端的部分,由类库最终使用者编写。
我们以钓鱼的例子演示
有4个类:动物(Animal),人(Person),鱼(Fish),鱼饵(Bait);
依赖关系为:人、鱼装载期依赖动物,人、鱼运行期还要依赖鱼饵。
可表示为如下图例:红色代表装载期依赖(强依赖),浅绿色表示运行期依赖(弱依赖):
这种关系的编程定义如下:(包定义__$package.js):
Fish 类加载过程的伪码表示:
//Fish 类加载的伪码表示 //确保编译器依赖的动物类加载成功 ensureClassLoaded('Animal'); //声明依赖的Animal类,并给变量赋值, //如 var Animal = __$tempPkg.classData['Animal']; declareAndAssignValiable('Animal'); //正式加载Fish类 loadClass('Fish'); //声明Fish类运行期依赖的Bait类(但为赋值) declareValiable('Bait'); //准备给Bait赋值的钩子函数(为了解决循环依赖问题) prepareValibaleAssignHook('Bait'); //执行钩子函数并删除临时变量 executeHookAndClearTemVariables(); |
本文中的实例程序,同框架一起打包,可从如下网址下载:https://sourceforge.net/project/showfiles.php?group_id=171424(请下载非压缩版:JSPackager-1.x-alpha-uncompress.zip,因为压缩版的查看代码非常不便,不利于学习)
JSPackager动态加载脚本需要使用到XMLHTTP技术,所以这些实例不能直接在文件系统上测试,应该把测试文件放在某个web服务器上,IIS、Tomcat、Apache等等,任何WEB服务器都行。
如果您机器上没有安装任何web服务器,那么我们的例子中带有的一个jar应用程序(TestServer.jar),只要你安装了Java环境,你就可以运行他(双击或者java -jar TestServer.jar),他将把当前目录作为Web服务器的内容目录启动。
JSPackager对目录设置没有什么特别的要求,实例中的目录是:
脚本目录放在scripts内,根据包名映射到具体子目录,html文件放在example目录下。
这里我们编写一个简单的脚本,显示“Hello Word”,能后把这个功能脚本集成到本框架中,展示一下本框架基本的功能,给您一个初步的印象。
实现脚本(HelloWord.js):
/** * 显示信息Hello Word */ function sayHello(){ alert("Hello Word"); } |
传统使用方法(hello-word-old.html):
<html> <head> <title>Test Hello Word</title> </head> <script src="../scripts/cn/opencode/example/HelloWorld.js"></script> <script> sayHello(); </script> <body> </body> </html> |
使用JSPackger集成的使用方法:
1. 定义包(__$package.js):
//添加脚本及其元素 this.addScriptObject("HelloWorld.js","sayHello"); |
2. Html代码:
<html> <head> <title>Test Hello Word</title> </head> <script src="../scripts/startup.js"></script> <script> $import("cn.opencode.example.sayHello"); sayHello(); </script> <body> </body> </html> |
相比之下,使用JSPackager管理之后,代码不需要做什么改动,只需要添加一个包定义文件,能后把原来页面的script元素去掉,添加JSPackager的引导脚本和导入语句。导入脚本时使用的就是脚本路径映射的包名。
这个例子可以展示一下JSPackager的编码方式,由于脚本过于简单,不好展示它作为一个脚本管理框架的有点。
3.3 鱼 我所欲也――展示依赖管理
这里我们详细介绍一个上面提到的钓鱼实例,展示JSPackage的包定义,依赖管理。导入语法。
我们例子的内容是:
//创建渔夫 var fisher = new Person(); //开始钓鱼 fisher.fish();
//创建鱼儿 var fish = new Fish(); //鱼儿上钩 fish.eat(fisher.bait);
//打印渔夫的钓鱼成果 alert(fisher.acquisition); |
先创建这个例子中需要的4个类的代码:动物(Animal),人(Person),鱼(Fish),鱼饵(Bait);
动物类 (Animal.js):
/** * 动物类 * @constructor */ function Animal(){ } /** * 休息行为 */ Animal.prototype.sleep = function(){ //sleeping...; } /** * 进食行为 * @param food 食物 */ Animal.prototype.eat = function(food){ //eat....; } |
人类Person (Person.js):
/** * 人类 */ function Person(){ } /** * 人类也是动物 */ Person.prototype = new Animal(); /** * 钓鱼 */ Person.prototype.fish = function(){ this.bait = new Bait(this); } /** * 收获 */ Person.prototype.acquisition = null; |
鱼类Fish (Fish.js):
/** * 鱼类 * @constructor */ function Fish(){ } /** * 鱼类也是动物 */ Fish.prototype = new Animal(); /** * 进食,有误食鱼饵的危险 */ Fish.prototype.eat = function(food){ if(food instanceof Bait){ //误食鱼饵,被抓 food.owner.acquisition = this; }else{ //do eating } } /** * 定义Fish对象作为String操作的时候的值s */ Fish.prototype.toString = function(){ return "Fish instance"; } |
鱼饵Bait (Bait.js):
/** * 鱼饵 * @constructor * @param owner 下饵者 */ function Bait(owner){ /** * 下饵者 */ this.owner = owner; } |
按照普通的做法,我们可以以如下方式编码:
<!-- Animal脚本必须放在Person、Fish 之前 --> <script src="../scripts/cn/opencode/example/fish/Animal.js"></script> <script src="../scripts/cn/opencode/example/fish/Person.js"></script> <script src="../scripts/cn/opencode/example/fish/Fish.js"></script> <script src="../scripts/cn/opencode/example/fish/Bait.js"></script> <script> //创建渔夫 var fisher = new Person(); //开始钓鱼 fisher.fish();
//创建鱼儿 var fish = new Fish(); //鱼儿上钩 fish.eat(fisher.bait);
//打印渔夫的钓鱼成果 document.write("<h1>钓鱼收获: "+fisher.acquisition+"</h1>"); </script> |
可以看到,上面的例子根本没有JSPackager的影子,现在我们要将这个普通脚本交给JSPackager管理。
1. 创建包元素信息及依赖描述
在本装包系统中,我们以脚本的相对目录路径为包名,在包目录下设置一一个__$package.js文件,在里面编写包定义脚本(this指向当前$JSPackager.PackagerInfo对象):
//添加动物(Animal)类 this.addScriptObject("Animal.js","Animal"); //添加人(Person)类 this.addScriptObject("Person.js","Person"); //添加鱼(Fish)类 this.addScriptObject("Fish.js","Fish"); //添加鱼饵(Bait)类 this.addScriptObject("Bait.js","Bait");
//人类,鱼类,扩展自动物类,这在装载期就需要使用Animal类的构造函数 //给人类添加对动物类的装载期依赖 this.addScriptDependence("Person.js","Animal.js",true); //给鱼类添加对动物类的装载期依赖 this.addScriptDependence("Fish.js","Animal.js",true);
//人类钓鱼时(fish())需要用到鱼饵的构造函数 //给人类添加对鱼饵类的运行期依赖 this.addScriptDependence("Person.js","Bait.js",false); //鱼类进食时(eat())要判断食物是否时鱼饵 //给鱼类添加对鱼饵类的运行期依赖 this.addScriptDependence("Fish.js","Bait.js",false); |
2. 改写脚本
<script src="../scripts/startup.js"></script> <script> //导入下列脚本中将直接引用的类Fish和Person $import("cn.opencode.example.fish.Fish"); $import("cn.opencode.example.fish.Person"); //创建渔夫 var fisher = new Person(); //开始钓鱼 fisher.fish();
//创建鱼儿 var fish = new Fish(); //鱼儿上钩 fish.eat(fisher.bait);
//打印渔夫的钓鱼成果 document.write("<h1>钓鱼收获: "+fisher.acquisition+"</h1>"); </script> |
可以看出,html代码更为简洁,最终用户已经可以不用关心所用类库的依赖了(Animal、Bait类就不需要显示加载了),这里还有一些没有显示出来的好处,当前全局变量中有类Person、Fish,确没有类Animal、Bait,虽然我们间接地使用到了他们,这里我们模拟出了一个保护域。做到了尽量少污染全局环境,这种方式可以用于解决某些类库间变量冲突的问题。我们甚至可以做到加载同一种类库的两个不同版本,不相互影响。
使用JSPackager唯一多出的负担就是编写包定义文件,不过想想这种定义文件可是一劳永逸的(以后就不需要每次导入脚本的时候都小心翼翼的判断那个脚本先导入那个后导入,有那些相关的需要导入,等等),而且有了包结构后对于代码组织、重用,以及文档的编写阅读,都将非常有利。
如前言所述,我们不大算编写丰富的基础API,但是我们可以集成其他成熟的类库,这里我们详细介绍一个封装的实例:类似Windows XP文件浏览器左侧的滑动折叠面板(任务菜单)效果。
我们先集成Scriptaculous Effect类库,并且在这个基础上按我个人的习惯对一个面板折叠效果做一个简单的封装,展示框架的类库封装功能。
1. 集成Scriptaculous类库:
这里我们不做过多介绍,详细情况请参考集成实战;我们发布的版本中已经把Scriptaculous放置于us.aculo.script包中,你可以把这些作为系统内置的类库使用。
2. 编写我们的折叠面板函数(cn/opencode/example/display/effect.js):
/** * 滑动面板实现. * 当指定元素可见时,将其第一个子元素向上滑动至完全被遮掩(折叠)。 * 当指定元素不可见时,将其第一个子元素向下滑动至完全显示(展开)。 */ function slidePanel(panel){ panel = $(panel); if(panel.style.display=='none'){ //调用Scriptaculous Effect的具体滑动展开实现 new Effect.SlideDown(panel); }else{ //调用Scriptaculous Effect的具体滑动闭合实现 new Effect.SlideUp(panel); } } |
3. 编写包定义脚本(cn/opencode/example/display/__$package.js):
//添加slidePanel(滑动面板控制)函数 this.addScriptObject("effect.js","slidePanel",null); //给effect.js脚本添加对us.aculo.script包中effects.js脚本的装载期依赖this.addScriptDependence("effect.js", "us/aculo/script/effects.js",true); |
4. 编写我们的HTML代码:
<html> <head> <title>重用aculo Effect脚本实例</title> <link rel="stylesheet" type="text/css" href="/styles/default.css" /> </head> <script src="/scripts/startup.js"></script> <script> $import("cn.opencode.display.slidePanel"); </script> <body> <div class="menu_header" onclick="slidePanel('menu_block1')"> 面板 1 </div> <div class="menu_block" id="menu_block1"> <ul> <li>text1</li> <li>text2</li> <li>text3</li> </ul> </div> </body> </html> |
onclick="slidePanel('menu_block1')"这个事件函数将在我们点击面板标题时触发,能后会调用Scriptaculous Effect的具体实现实现我们需要的滑动折叠功能。
从这个实例可以看到,在使用JSPackager之后,很多细节我们可以在包中封装掉,不需要告诉类库使用者太多。大大降低类库的使用复杂度。同时,类库封装的支持可以让我们很轻松的按自己的喜好封装自己的类库接口。
4.1 集成Prototype、Scriptaculous脚本实例
Scriptaculous是一个基于Prototype的一个脚本库。提供网页上的托拽,显示效果,UI组件等方面的支持。本实例介绍一下这两个类库如何与本框架集成。
本实例中用到的的Scriptaculous脚本依赖于Prototype 1.5,我们将prototype脚本放置在如下位置/scripts/net/conio/prototype/v1_5/prototype.js,声明prototype的包定义文件__$package.js:
this.addScriptObject("prototype.js", ['Prototype','Class','Abstract','PeriodicalExecuter', '$','$break','$continue','Enumerable','$A','Hash','$H', 'ObjectRange','$R','Ajax','Toggle','Insertion','Element', 'Field','Form','$F','Event','Position','$$']); //prototype1.5 补丁 this.addScriptObject("__prototype.js", ['Event','Element','ObjectRange']); this.addScriptDependence("prototype.js","__prototype.js",true); this.addScriptDependence("__prototype.js","prototype.js",false); |
注:Prototype有几处不规范的代码,导致了一些问题,我们为之制作一个补丁:__prototype.js,细节见附录 Prototype补丁说明:
这样prototype所在的包为net.conio.prototype.v1_5,但是让用户死死记住一个类库的版本号码,不是一个好的做法,为此,我们设置一个抽象包net.conio.prototype 来指向prototype1.5这个具体的实现:在/scripts/net/conio/prototype/目录中添加包定义脚本__$package.js:
this.setImplementationPackage(".v1_5"); |
本实例中用到的的Scriptaculous版本是1.6.1,我们吧他的全部脚本文件拷贝到如下位置 /scripts/us/aculo/script/v1_6_1,声明Scriptaculous的包定义文件__$package.js:
this.addScriptObject("builder.js","Builder"); this.addScriptObject("effects.js","Effect"); this.addScriptObject("effects.js","Effect2","Effect"); this.addScriptObject("dragdrop.js","Droppables"); this.addScriptObject("slider.js","Control"); this.addScriptObject("controls.js","Autocompleter"); this.addScriptObject("controls.js","Ajax");
this.addScriptObject("unittest.js","Test");
this.addScriptDependence("*","net/conio/prototype/prototype.js",true); this.addScriptDependence("effects.js","builder.js"); |
与Prototype相同的理由,我们声明一个抽象包 us.aculo.script在/scripts/us/aculo/script/目录中添加包定义脚本__$package.js:
this.setImplementationPackage(".v1_6_1"); |
至此Prototype Scriptaculous两个脚本库已经成功集成至本系统中,你现在可以检测一下集成的结果。
简单起见,我们可以修改(将原来的<script>元素去掉,换成系统的启动脚本和导入语句)Scriptaculous自带的测试例子来测试我们使用JSPackager之后的效果,我们将rest目录拷贝至我们的网站某个目录中(如:/test/aculo)。
其自带的例子分两类,一种是功能演示性测试:test/run_functional_tests.html, test/ functional/*; 另外一种是单元测试型测试:test/run_unit_tests.html, test/ unit/*;
原测试文件中原来的脚本一般为:
<script src="../../lib/prototype.js" type="text/javascript"></script> <script src="../../src/scriptaculous.js" type="text/javascript"></script> <script src="../../src/unittest.js" type="text/javascript"></script>
|
现在修改成:
<script src="/scripts/startup.js" type="text/javascript"></script> <script type="text/javascript"> $import("us.aculo.script.*");
//该导入指令只是某些直接用到prototype类库的网页需要 $import("net.conio.prototype.*"); </script> |
运行测试,如果您看到效果与原来无异。说明集成成功。
我们也可以运行他的测试用例,可以看到,结果与原来方式无异(loading_test.html 比较特殊,他没有导入全部类,测试的是某些类的确失去,确保不加载无关类,所以我们也不要导入全部的类,不过这个测试对我们没有意义)
5.1 $import 函数用法
导入指定元素(函数、类、变量)至制定目标,目标默认为this。 我们推荐在全局上下文做导入工作。 java 的导入指令在类声明之外,我们也希望用户把导入脚本放在任何函数之外。 上述理想情况下,target一般不指定,这样target将是默认值 this = window;
导入的工作将分两步进行:
·确保元素已加载,为加载即加载之
·将该元素声明至指定对象(通常情况下是window,即声明了全局变量)
this 引用说明:
·函数作为对象成员调用(如:obj.method() ): 其内部this为对象(obj), 但是this不会在调用栈上传递,即如果你在成员方法中调用了一个普通函数(如:func1() ), 那么在这个普通函数内部的this确不是该对象,一般为全局对象
·事件脚本:this为事件源(事件发出的HTML元素)
·自由脚本(页面上<script>元素引用的或直接书写的脚本):this为全局对象(window)
·托管脚本(通过$import直接或间接导入的脚本):this为该加载单元的脚本加载器对象(ScriptLoader)
全局对象(window)的特殊性:
全局对象(window)同时还是全局执行上下文对象, 全局对象(window)的属性或函数同时也是一个全局变量或函数,可以在任何地方直接使用, 这样就产生了一种特殊的情况: 我们的$import函数在自由脚本中调用,默认导入成window属性时, 等价于声明了一个全局变量,这给类库使用者带来了不少方便。
该方法为最终用户设计,不推荐类库开发者使用该函数, 类库开发者可在包中定义脚本依赖完成类似功能。(注:包类调用时this为脚本加载器)
Parameters:
path - (package.ClassName|package.*)
taget - 可选参数(默认为this,不过对于非对象成员函数调用时,this == window == global context,所以说,这种情况等价于直接声明的全局变量),指定导入容器。
这段脚本将在包描述对象($JSPackager.PackagerInfo)的构造时调用,所以这段脚本中的this指向的是当前包描述对象。
例:
//设置当前包的日志级别为DEBUG(0) this.setLogLevel("DEBUG"); //添加动物(Animal)类 this.addScriptObject("Animal.js","Animal"); //添加人(Person)类 this.addScriptObject("Person.js","Person"); //添加鱼(Fish)类 this.addScriptObject("Fish.js","Fish"); //添加鱼饵(Bait)类 this.addScriptObject("Bait.js","Bait");
//人类,鱼类,扩展自动物类,这在装载期就需要使用Animal类的构造函数 //给人类添加对动物类的装载期依赖 this.addScriptDependence("Person.js","Animal.js",true); //给鱼类添加对动物类的装载期依赖 this.addScriptDependence("Fish.js","Animal.js",true);
//人类钓鱼时(fish())需要用到鱼饵的构造函数 //给人类添加对鱼饵类的运行期依赖 this.addScriptDependence("Person.js","Bait.js",false); //鱼类进食时(eat())要判断食物是否时鱼饵 //给鱼类添加对鱼饵类的运行期依赖 this.addScriptDependence("Fish.js","Bait.js",false); |
void addScriptObject(scriptPath,classNames,realNames)
添加类。 需要指定脚本位置(必须在当前包目录中),元素名(可用数组,同时指定多个,但这时,realName参速将被忽略)。 如果实际脚本中的变量名并不是你希望添加的名字,你需要指定一下脚本中的真实变量名。 该成员函数只在包定义文件(__$package.js)中调用
Parameters:
scriptPath - 指定脚本路径
className - 类|函数名 或其数组,若为数组,realName参数将忽略
realName - 可选(默认同名)
void addScriptDependence(thisPath,targetPath,requiredBefore)
添加脚本依赖。 需要指定当前脚本位置(必须在当前包目录中)、 被依赖的脚本位置(当前包中的脚本,或者通过抽象路径指定其他包中的脚本)、 是否需要执行前导入(装载期依赖)。 该成员函数只在包定义文件(__$package.js)中调用
Parameters:
thisPath - 本包中当前脚本文件(*.js),使用*可表示当前该包中已添加全部脚本文件(将逐一添加同样的依赖)。
targetPath - 依赖的脚本文件抽象路径(可不包括最后的版本包)
requiredBefore - 可选参数(默认为false) 是否需要执行前导入(装载期依赖)
void setLogLevel(level)
指定包的默认日志输出等级,每个加载单元中有一个独立的$Packager.Log的实例,变量名为$log。 通过这个函数我们可以设置当前包的默认日志的输出级别(可被脚本的设置、全局设置覆盖)。 该成员函数只在包定义文件(__$package.js)中调用
Parameters:
level - 日志输出级别 DEBUG : 0,INFO : 1,WARN :
2,ERROR : 3,可用数字或字符串表示如 this.setLogLevel(0);
this.setLogLevel("DEBUG");
void setScriptLogLevel(scriptFile,level)
指定包的日志输出等级,每个加载单元中有一个独立的$Packager.Log的实例,变量名为$log。 通过这个函数我们可以设置包中指定脚本的日志的输出级别。 全局Log设置可以覆盖所有包中的设置(${scriptPath}/log.js) 该成员函数只在包定义文件(__$package.js)中调用
Parameters:
scriptFile - 可选参数(默认为设置的包中所有脚本的默认值) 指定脚本的日志级别 this.setLogLevel("Class1.js",0); this.setScriptLogLevel("Class1.js","DEBUG");
level - 日志输出级别 DEBUG : 0,INFO : 1,WARN : 2,ERROR : 3,可用数字或字符串表示如
void setImplementationPackage(pkgPath)
设置具体实现包名。 比如,我们可以给prototype库一个统一的包, 但是我们的内容都放在具体的实现版本里, 我们可以通过该设置(setImplementationPackage(this.name+".v1.5");)来指定默认的具体实现版本。 该成员函数只在包定义文件(__$package.js)中调用
Parameters:
pkgPath - {String} 指定实现包名,全路径(ID(.ID)*)或相对路径("." 开始的为本包下的相对路径)
接口包,是指没有放置具体脚本的包。该包中将声明一个指定的实现包。
接口包只是一个用户导入操作时的一个别名(与编程语言中的概念不尽相同),实现包中才是真正的内容。
常见情况是类库的版本管理,比如某个用户在用prototype库,具体版本是1。5,但是一般情况下不想让用户知道这些细节(也允许使用其他版本),于是,可以声明一个抽象包:net.conio.prototype,指向具体的实现包:net.conio.prototype.v1_5。一般情况下,用户只要导入net.conio.prototype中的类即可, 而且我们还可以吧其他版本放上去,已备万一之用。
net/conio/prototype/__$package.js
this.setImplementationPackage(".v1_5"); |
net/conio/prototype/v1_5/__$package.js
this.addScriptObject("prototype.js", ['Prototype','Class','Abstract','PeriodicalExecuter','$','$break','$continue','Enumerable','$A','Hash','$H','ObjectRange','$R','Ajax','Toggle','Insertion','Element','Field','Form','$F','Event','Position'], null); |
JSPackager中对不支持XMLHttpRequest的浏览器(IE)编写了一个模拟构造器,统一了XMLHtTTPRequest的创建方式.使得用户在常用浏览器上可以使用W3C推荐的标准方式创建XMLHttpRequest对象:
var request = new XMLHTTPRequest(); request.open(“post”,”test.jsp”); request.send(“name=jindw&email=jindw@xxx.com”); |
$Request对象,可以通过他更加简便的使用XMLHTTPRequest对象,该对象既可以同步的方式调用,直接通过其取值函数获取请求数据(getText、getXML、getResult、evalResult……);也可以使用异步的方式调用,此时其根据状态的改变回调该请求对象的时间函数,依次是: onStart -> onReceiving -> onSuccess/onFailure -> onFinish
例:
//同步方式,我们直接获取请求数据 var jsonObj= new $Request(“test-json.jsp”) |
//异步方式,通过我们的事件函数完成需要的工作 new $Request(“test.jsp”,{asynchronous:true}) .setSuccessHandler(function(){ alert('success,the content is' +this.getText()); }) .setFailureHandler(function(){ alert('fail,the content is' +this.getText()); }) .send({param1:1}); |
url:请求地址
options:请求选项
默认值为:
{ method: 'get', asynchronous: false, contentType: 'application/x-www-form-urlencoded', headers:{"Accept":"'text/javascript, text/html, application/xml, text/xml, */*'"} } |
$Request默认就是同步方式调用,所以同步调用时参数({asynchronous:false})完全可以省略,(见附录:我们的$Request封装为什么默认为同步方式?)。
$Request 对象的大部分常用方法都将返回$Request对象本身,这点类似于java.lang.StringBuffer (append\insert等方法)。这样,我们可以吧一系列操作(构造$Request对象,设置事件处理函数,发送请求等)放在同一行代码内完成,通常情况下,我们甚至不需要声明$Request对象的临时变量即可完成我们需要的工作,使得代码更为简洁。
$Request对象有一些列的set*Handler方法,其实只是吧一些函数赋值给onStart -> onReceiving -> onSuccess/onFailure -> onFinish等属性,我们完全可以直接赋值,但是难免出错(拼写错误,大小写错误等等),而且运行起来有时还不容易发现这些错误,但是加入我们用set*Handler系列方法去赋值,那么属性记错了就是错了,很容易发现,而且还可以兼具上面说的方法连续调用的优点(可联系赋值)。
该函数用于同步加载简单数据(使用XMLHTTPRequest.getText()实现)
我们编写了一个简单的测试,用于测试指定包下面全部对象都能通过$import函数单独加载(需要包定义文件__$package.js编写正确),可以简单的测试脚本的装载期依赖的完整性,但是对于具体实现的问题和运行期依赖测试,需要您自己编写具体单元测试。
测试自己编写的类库时,可以直接在test/test-package-object-load.html中添加你自己的包,运行测试即可。
<script> var console = document.getElementById("log"); $JSPackager.setConsole(console); $import("test.loader.*"); LoaderTest.test(["net.conio.prototype", "us.aculo.script", "cn.opencode.example.fish", "cn.opencode.example.display", "cn.opencode.example"]); </script> |
全局环境和每个脚本装载器中都有一个独立的$Packager.Log的实例,变量名为$log。
日志级别分为 DEBUG : 0,INFO : 1,WARN : 2,ERROR : 3,NONE:4,
可以通过$JSPackager对象的静态函数setScriptLogLevel(pattern,level)指定全局日志级别设置。Pattern为类似ant中的目录设置那样的匹配模式(*代表一级目录或目录下的文件,**d代表任意级目录或文件),用于匹配脚本。如果多个模式同时匹配,以最长的模式为准。
可以在包定义文件中通过this.setLogLevel、this.setScriptLogLevel成员函数设置默认的日志属性,不过这些设置会被全局设置覆盖。
全局日志输出默认为INFO,您可以在全局环境中调用$log.setLevel()函数设置。
记录日志时我们调用$log对象的成员函数error、info、warn、debug输出日志,参数任意个数任意类型。如:
$log.error(msg1,msg2,msg3);
同Prototype,绑定this对象和参数列表,第一个参数为thisArg,其他为原函数参数。
类似Prototype中的$(),便捷的方式获取指定id的HTML元素。但是我们不接受多个参数,我们认为那样复杂化了。
等能你可以导入Prototype中的$函数覆盖框架的$,那样不会有任何问题。
让简单的事情更简单,让复杂的事情可实现。
对于私有方法,尽量使用强类型,不做容错处理
对于公开方法,做容错处理,尽量兼容各种可能的用户使用习惯。
目录路径以/结尾
框架全局变量
$abc 变量(函数)
$Abc 类(构造器) | 静态抽象类
$abc.ABC | $ABC 常量定义
局部变量
abc 变量(函数)
Abc 类(构造器)
私有全局变量(全局有效,但是不希望外界使用)
临时属性变量(某些操作需要给某个对象添加一个临时属性,操作结束后需要清理的)
私有未知域有效变量(不希望外界显示调用,但框架可能需要这些变量来暂存信息,如临时存储一个包数据)
__$abc
__$Abc
理由是,虽然大家都说异步方式如何如何优于同步方式,但是我们认为,一步方式更难以控制,增加了程序员的编程难度,容易犯错,所以我们默认采用同步方式发送请求,如果硬是需要异步处理的话,那就麻烦您设置一下参数,这也反映出我们系统的一条设计原则:让简单的事情更简单,让复杂的事情可实现。
Prototype有几处不规范的代码,导致了一些问题,我们为之制作一个补丁:__prototype.js。
内容如下:
//prototype1.5 补丁 /** * Prototype 在if块里声明了Event对象,但是有可能这个块没有执行, * 但是更具js的特性,if块中的变量声明即使没有执行,局部变量也将存在,但值为空。 * Prototype在全局上下文环境执行时,刚好会跳过这一错误。 * 但托管情况下不行,所以我们加上如下变量初始化,避免空指针 */ var Event = window.Event; /** * 同上 */ var Element = window.Event; /** * 该变量Prototype没有声明,我们不想起污染全局环境,所以加上声明。 */ var ObjectRange; |
这些代码需要在prototype加载前执行,且不希望补丁单独加载;所以,我们需要定义一个prototype对补丁的装载期依赖,和一个反向的运行期依赖。
………………. //prototype1.5 补丁 this.addScriptObject("__prototype.js", ['Event','Element','ObjectRange']); this.addScriptDependence("prototype.js","__prototype.js",true); this.addScriptDependence("__prototype.js","prototype.js",false); |