关于JavaScript和Dojo的一点备忘
关键词:JavaScript,Dojo
作者:BIce 创建时间:2012-01-14 03:10:00
1. 前言
由于我在2010.7至2011.7之间,有幸参与了一个Web项目的开发,一直想找个机会总结一下关于这个项目的收获,一直也没有找到时间,正好最近比较闲,就简单总结一下。由于一直低头做业务,项目也比较紧,就对技术上的了解不多,仅仅是了解一些简单技术,但也小小的总结一下吧,在以后的工作中一定注意,要对基础技术有了解,不能仅仅知道怎么用,该怎么开发就可以,一定要深入挖掘可学习的东西,这样才能真正的有技术上的提升。
由于项目主要采用前端Dojo+后端SSH的解决方案,而我主要参与的工作是前端的Widget开发、后端的业务逻辑部分、代码编译打包以及自动化测试部分,而在这些工作中有大概半年的时间都在做前端的Dojo Widget开发,本文主要就记录关于JavaScript和Dojo的一些内容吧。关于项目打包发布和自动化测试在其他的文章里有说过,这里就不再赘述。
说到Dojo,就不能不说JavaScript,Dojo是一个JavaScript语言的重量级工具箱,其中提供了丰富的控件和比较全面的前端解决方案,它为我们以简单的方式使用JavaScript语言带来了很大的便利。鉴于Dojo基于JavaScript语言,本文就主要简单记录自己所了解的JavaScript语言的一些语言特性以及Dojo相关的一点知识。
2. JavaScript与DOJO
首先描述JavaScript语言与Dojo的关系,JavaScript是一门Web浏览器的语言,而Dojo是基于JavaScript的一个重量级应用工具集。
2.1 JavaScript简单语言特性
谈到JavaScript,首先要说的是它是一门Web前端脚本语言,是脚本就表名了它只有解释器,没有编译器和链接器,是解释执行。其他特性:
1. 基于对象:
1) 请注意不是“面向对象”,JavaScript使用了对象的概念,但是和传统的面向对象编程语言如Java是完全不一样的,我们知道在面向对象的语言中,非常重要的两个概念就是:类、继承。但在JavaScript甚至没有“类”的概念,只能模拟实现,关于在JavaScript中如何使用对象在后面说明。
2) 在JavaScript中除了基本类型除外,所有的事物都是对象,函数是对象,数组是对象,正则表达式也是对象。而在JavaScript中,没有类的概念,类也是对象。对象通过引用来传递,它们从来不会被拷贝,也就是说一种对象只有一个副本。
3) 在JavaScript语言中,对象是可变的Key/Value集合,其中Key可以是包括空串在内的任何字符串(如果传入的不是string,则会被转换成string当作Key来使用)。
2. 基于原型:在JavaScript中,关于继承的实现是通过原型(prototype)来实现的,每个对象都连接到一个原型对象(prototype object),并从它中继承属性,默认对象都连接到Object.prototype这个原型上,其他的扩展原型还有Array.prototype和Function.prototype等等,JavaScript的继承也采用原型继承,具体见下面说明。
3. 函数式语言:是一个函数式语言(lambda语言),与Lisp有很多相同点
4. 动态性:动态对象,在原型对象(prototype object)上的修改会马上反映到所有prototype指向它的对象,这些对象从prototype处获得的属性都会随prototype的修改而修改。
5. 没有块级作用域:虽然在语法上JavaScript采用了块语法,但是它却没有提供块级作用域,代码块中声明的变量在包含此代码块的函数中的任何位置都可以使用。所以在JavaScript中在使用变量时再声明变量是不正确的,应该在每个函数的开头声明所有需要的变量。
6. 弱类型&高容错:JavaScript语言的类型是弱类型,变量声明时不需要指定类型名,在具体使用时会被转换成合适的类型,解释器也不会去校验类型是否匹配,当然这样也就无法在静态分析时发现一些错误。而且JavaScript的容错性也很强,它可以处理很多在如C/C++语言中不会允许的错误情况,比如它会给没有分号的语句自动加上分号来保证程序正确,不过这样也给某些莫名错误的出现打下了基础。
7. 使用全局变量:JavaScript实现依赖于全局变量,它没有链接器,所有的编译单元的顶级变量都被放到全局变量的命名空间中,在Web浏览器中,全局对象名为window对象。另外关于全局变量的声明一般有三种办法:
1) 不在函数内使用一个var语句:var foo=value,这样会定义一个全局变量在window下
2) 直接在window下添加一个属性 :window.foo=value
3) 直接使用未经声明的变量:foo=value
以上三种方式的结果是等价的。
8. 单线程执行:JavaScript的执行是单线程的,没有并发的问题。
9. 数字只有双精度浮点:JavaScript中数字只有双精度浮点这一种存储格式,但是也提供了一些获取整型部分进行整形运算的方法。
10. 支持闭包:在下面介绍。
2.2JavaScript函数对象
在JavaScript语言当中,所有的非基础类型数据都被看成对象,函数也不例外,而且由于JavaScript有函数式语言的特性,所以描述函数对象就十分重要,在下面简单记录函数对象的基本内容:
函数对象的声明方式类似:var funcA =function(a,b){…}.
在JavaScript解释器遇到function funcA(){…}的函数定义时,内存会生成一个作为原型进行使用的funcA对象。(在遇到new运算符时,用此原型来创建实例,此原型唯一.)
函数对象的prototype属性默认连接到Function.prototype。
在每个函数创建时都有两个附加的隐藏属性:函数的上下文和函数的代码部分。
2.2.1闭包
在嵌套定义函数的时候,比如在Func1中又定义了Func2,由于Func2可以访问Func1中的变量,则可以构造一种Func2的作用域超过Func1的方式:让Func1的返回值返回一个Func2的对象,而Func2中又引用了Func1的局部变量,这样从Func1中的返回值得到的是一个Func2的函数,Func2还可以使用Func1中函数内部变量,(由于有引用依赖无法垃圾回收),这样我们就得到了一个函数Func2,只有它可以访问Func1中的局部变量的特权。一般像这种函数可以访问它被创建时所处的上下文环境,我们称之为闭包。它是JavaScript语言表现力的基础,非常重要的特性,很多高级功能由它而来。典型应用:setTimeout方法中的绑定。
2.2.2函数的四种调用方式
在JavaScript语言中,一个函数对象被调用有四种方式。我们说过函数也是对象,那么既然是对象就也有this引用来访问自身,上述提到的四种方式的区别就在于this的初始化。(JavaScript采用延时绑定,在调用函数的时候this才绑定)。下面一一介绍这几种调用方式。
1. 方法调用模式------ object.method();
此方式用于调用某个对象的方法,在此种使用情况下this被绑定到object对象上。
2. 函数调用模式------ method();
当一个函数并非一个对象的属性时,使用此种调用方法,这种情况下this被绑定到全局对象上,也就是window对象
3. 构造器调用模式------new method();
首先此种调用方法会创建一个对象,此对象的原型属性会连接到method函数对象上,同时this会绑定到这个新对象上。而这种使用new调用的函数被称为构造器函数(constructor),通常名字应该大写
4. Apply调用模式------method.apply(objectThis, params)
这是一种函数式的调用方法。使用这种调用方式我们可以指定this的绑定值,还有传递给方法的参数数组。
2.3JavaScript与DOJO实现对象机制区别
像我们知道的,JavaScript采用的是原型继承,它的基本实现方式是原型链(prototype chain)。每个对象都会连接到一个原型对象,并且可以从中继承属性。
2.1.1 JavaScript继承
JavaScript的原型继承可以用来模拟类,一般的模拟方法为使用Function对象来模拟类(Function对象的存在目的就是为模拟类提供一个立足点:它的实例可以作为构造函数与new运算符连用以创建对象实例,同时也为创建的对象实例通过一个模板)。
典型的JavaScript类:Shape类
function Shape(centerX,centerY,color)
{
this.centerX=centerX;
this.centerY=centerY;
this.color=color;
}
//创建实例
s = new Shape(10,20,’blue’);
典型的JavaScript继承
//定义子类
function Circle(centerX,centerY,color,radius)
{
this.base=Shape;
this.base(centerX,centerY,color);//调用超类的构造函数
this.radius=radius;//加入新属性
}
//定义继承关系
Circle.prototype=new Shape //将子类的原型对象重写为超类的实例
//创建实例
c= new Circle(10,20,’blue’,2);
由上可见,一般的JavaScript类模拟和继承需要使用Function对象和原型链的修改,而且实现的结果看起来不直观,用起来很麻烦。
2.1.2 Dojo 继承
Dojo基于本地JavaScript实现提供了模拟类和基于类的继承机制,使得我们对于继承的使用与C++/Java更为接近,方便大家编程,主要提供的定义类的API是dojo.declare.将在下面介绍
Dojo对于继承方式主要采取mixin混入模式和委托模式来完成属性的继承。
1. 混入模式
Dojo的混入模式主要使用的api为dojo.mixin.这个函数有两个参数,它会把第二个对象的属性全都添加到第一个对象中,通过这种方式,可以实现子类对父类属性的继承,当然这种继承是通过拷贝父类属性来完成的。
2. 委托模式
Dojo的委托模式主要使用的api为dojo.delegate.这个函数有2个参数,返回得到的子类,它可以指定由第一个参数提供相应的属性,而第二个参数为子类中保存的属性。(一些属性由第一个参数对象委托给出),此种方式不会拷贝父类属性,仅仅会混入子类自己属性
dojo.declare
Dojo的面向对象的支持由此api给出,这个api使用了上述的mixin、delegate和extend等api和prototype chain技术提供了一个很好用的声明构造函数的简单方式。它的参数如下:
dojo.declare(
/*String*/ className,
/*Function|Function[]*/ superclass,
/*Object*/ props)
其中className是要声明的构造函数的名称,superclass表明要继承的父类或者多重继承父类,props为一个属性对象(主要的属性、方法都要在此处定义),其中的属性会被复制至构造函数的prototype属性中。使用这种方式我们就可以很方便的定义出“类”,以及它们之间的继承关系而不用去考虑其他方面了。
dojo.declare方法生成构造函数获得的主要方法
在我们使用dojo.declare方法生成构造函数时,默认的产出构造函数的prototype中有这么几个函数需要注意:
1. preamble:先于constructor执行
2. constructor: dojo生成的此类的构造函数,它需要:触发superclass的构造函数,触发混入的构造函数,触发当前类的构造函数(有的话)
3. postscript:主要用于触发部件的创建
这三个函数会在初始化类对象时触发如:var foo=new YourDojoClass();
调用父类中被重写的方法
如果在子类中重写了父类的某个函数,但又想在子类重写的函数中调用父类函数时,可以使用inherited方法来调用超类中对应的被重写的方法(constructor自动调用,无须使用)
如:this.inherited(arguments) ;//调用父类被重写方法并传递本方法的参数
多重继承
我们知道如果使用原型链的方式,每个对象只有一个prototype属性,那么这样就不能在JavaScript中实现多重继承,但是Dojo通过混入mixin实现了多继承。在dojo.declare方法中的第二个参数中,如果是多重继承的话应该是个列表,其中dojo使用列表第一个元素作为实际的产生对象的prototype,而后面的元素则用混入技术将属性加入来模拟多重继承。
2.3Dojo的增强工件---Dijit
总的来说Dojo使我们能够以比较简单的方式去操作JavaScript语言。其他方面,Dojo还提供了非常多的控件,Dijit,即Dojo部件,它们可以用于丰富我们的Web界面,而且很多控件直接封装了AJAX的调用,也使我们的开发变得更为简单。说到Dijit就不能不说Dojo工具集中非常有用的DataGrid控件,它提供了类似.NET的表模式展现,可以非常容易的对表中数据进行展现和操作,非常好用。还有比如对于AJAX异步调用时的回调函数作用域的问题,处理起来很麻烦,Dojo就提供了一个dojo.hitch方法,可以让我们很容易的指定需要绑定的上下文。还有比如它提供的前台输入校验功能也是非常强大的。
Dojo所有的Dijit遵循相同的生命周期,想很好的使用它们必须要对其生命周期有所了解,下面简单记录Dijit的生命周期。
一般的Dijit都继承自dijit._Widget和dijit._Template这两个超类,其中
1. dijit._Widget中定义了几乎所有的生命周期方法,是最重要的超类
2. dijit._Template则主要用于处理解析和替换模板文件,它的相关概念有模板路径、dojoAttachPoint属性(寻找对象)、dojoAttachEvent属性(事件响应处理)
下面主要描述生命周期的相关方法:
1. preamble ,来自dojo.declare,主要用于在constructor接收到参数之前对参数进行处理,其返回值会被传递给constructor.
2. constructor,来自dojo.declare,对非原始类型的部件属性进行初始化,添加其他下游生命周期方法所需属性
3. postMixInProperties,来自dijit._Widget,在Dojo处理继承关系并向子类中混入所有基类属性时调用。
4. buildRendering,来自dijit._Widget,dijit_Template会重写此方法,以便完成和实例化部件模板相关操作。
5. postCreate,来自dijit._Widget,在部件已经完成创建并且显示在页面时进行,仅仅自己完成,子部件还可能未完成
6. startup,来自dijit._Widget,在当前dijit完全创建完成后自动调用,在此处可以安全的使用其他子部件dom,在此之前进行节点的操作都可能会出现错误。
7. destroyRecursive,来自dijit._Widget,为了清理部件及其子部件而调用的通用析构函数,此方法最好不要重写
8. uninitialize,来自dijit._Widget,重写此方法可在销毁部件时加入自定义的清理操作。
从郭老的PPT抄的两个图:Dijit的创建与销毁。
关于Dojo增强工件请查看Dijit中的各个内容,建议查看例子,决定不会让你失望。
3. 整体的架构体系
3.1整体描述
项目的整体采用传统的MVC架构进行,其中View部分采用了Dojo框架书写,Controller部分使用了Structs,(典型的前端控制器模式),Model部分使用了Hibernate,而Spring则完成了这些工具的配置整合工作。
3.2 View :DOJO
前端的框架中,每一个功能被实现为一个Widget,界面更换通过摧毁当前显示Widget然后新建Widget,初始化之后挂接到合适的节点上来完成。在与Controller交互方式上我们采用通用查询接口的方式,提供一个完整的支持各种查询的通用接口,统一处理来自客户端的请求(当然通用查询接口要加上权限控制,数据容错等等)
3.2Controller & Model : SSH
Struts 中主要完成Controller部分,由Action响应用户的请求,调用相关的业务代码来完成相关功能,而业务代码中调用Hibernate完成的数据的持久化。Hibernate另外的功能是可以按照面向对象方式管理数据库,使用起来很方便。
Spring的主要功能为IOC控制反转和AOP面向方面编程,我们的主要使用在于IOC部分,通过IOC来组装Struts/Hibernate以及其他部件,进行整体控制。
另外废话一句,在Java6加入了注释这个特性之后,SSH的使用变得方便了。
Json & Xml
关于数据传输,Json的传输速度比较快,比较灵活,而Xml的格式比较严谨,但使用起来略麻烦,这两种传输数据的方式各有其优缺点,都有很多的支持者,各种语言也基本支持。
关于XML,前端浏览器都有对于xml的内置支持,但由于各个浏览器对于js对Xml的处理实现有差异,所以在编写这部分代码的时候需要进行分浏览器处理,而XML相关的知识有XPath,XSLT等。Java后端的XML推荐用dom4j,挺好用的