2007-09-03

dojo学习笔记 (一)

最近几天开始学习dojo 0.9.0, 主要是看它的源码和测试样例, 以下是这几天的研究成果

一、 dojo 概述
我用的是dojo 0.9.0, 下载压缩包解压后有四个文件夹, 依次为:
dijit: 提供了很多ui控件, 被称为widget。
dojo: 提供了很多跨浏览器的实用函数,
dojox: 似乎是提供了一些插件性质的东西, 我只对dojox.flash有点了解
util:里面只有个doh, 功能不明

二、 dojo库的使用
首要的是在html里导入dojo.js文件, dojo加载支持很多参数, 其中我目前用到的有parseOnLoad和isDebug. 设定参数的方法有两种, 一种是在导入dojo.js前通过javascript建立一个对象djConfig,包含要设定的键值对,如djConfig={isDebug:true}; 另一种是在<script>标签里加入djConfig属性,值为JSON语法的键值对, 如<script djConfig="isDebug:true" src="dojo.js"></script>
一开始我对于后一种方法感到很神奇, 起初我以为javascript可以获得自己所在标签的信息, 后来看dojo.js, 查找djConfig, 发现它一上来就判断djConfig是否存在, 否则就建立新值,这时我认为djConfig标签可以作为一个对象在javascript中可以访问, 但亲自尝试后发现并不行。于是我甚至认为这是dojo的一个bug, 但在随后的研究中我发现它确实起作用,于是不得不再认真得看dojo.js(当然是dojo.js.uncompressed.js), 发现原来它会遍历所有script标签,并对导入dojo的标签的djConfig进行处理, 实际上就是加上一对括号然后eval一下。 此时我才恍然大悟。
再说说那两个参数, isDebug不必多说, parseOnLoad是指定dojo.js是否在html加载完毕后进行扫描,识别通过标记方法声明的对象(比如dijit的widget就都支持这种声明, 十分方便)如<div dojoType=dijit.form.Menu> </div>

三、dojo库的几个常用函数
1.dojo.connect(source_obj, event, func)或dojo.connect(source_obj, event, target_obj, target_method)
这个函数相当于把自定义的函数绑在source_obj.event上, 当此事件发生(即这个函数被调用)后,便会调用source_obj.func或target_obj.target_method, 而传进去的参数跟source_obj.event得到的参数相同。
这个函数十分方便, 也是我最早听说的几个dojo实用函数之一, 但关于其实现方法, 我是看了源码才明白。

2.dojo.provide(package_name)
这个是声明当前js提供的包名, 与dojo.require配合使用

3.dojo.require(package_name)
这个指定导入一个包, 这也是十分有用的, 一个是它非常智能, package_name一般为A.B.C的格式,我们暂且称A为bass_name, 它会根据A找到基准目录, 默认是按dojo.js所在路径加上../A,不过也可以用dojo.registerModulePath注册自定义的路径(dojo已内置了一些包的路径, 如dojo, doh)。有了基准路径后, 它会首先尝试B/C.js, 看是否成功, 否则再试B.js,如果有多极, 就逐层往上退。 如果一直找不到就报错。
另一个有用的地方是它可以实现动态导入和避免重复导入, 这一方面非常适合动态的Ajax, 又保证了性能。

4.dojo.declare(class_name, base_class, properties)
dojo提供的一个OOP机制, 而这个是声明一个新类, 举例说明:
dojo.declare("son", father, {
constructor:function(){
alert("hi");
}
});
这样就声明了一个名为son的类, 父类为father, 以后可以用 new son()来创建一个实例
另外base_class可以为null或数组, 后者时,里面必须全为function对象, 以后第一个元素为新类的父类, 其他类只是为了继承其方法。
关于继承, 首先constructor是构造函数, 不会覆盖父类的构造函数, 并且父类的构造函数会被自动调用, 而其他函数则会覆盖父类的。
此外, properties中也可以定义成员变量。

5.dojo.body(), dojo.doc
前者返回body的DOM node, 后者是当前document对象, 一般就是当前的document, 不过由于可以设置整体的上下文, 如dojo.setContext, 所以在dojo框架下应该尽量使用dojo.doc

6.dojo.byId(id_string)
应该比document.getElementById有所增强, 但无论如何, 精致的名字也是诱人之处。

当然还有很多其他函数, 比如dojo.io.bind。 只是我现在还没看到网络通信部分, 等下次再补上吧。

四、dijit库的使用
dijit库的函数我主要是用的dijit.byId, 注意他和dojo.byId的不同, 这里是根据id返回已经生成的dijit的widget对象, 并不是DOM node

dijit提供的控件主要有两种生成方法, 一种是用javascript直接生成, 如new dojo.form.Button(null, node), 另一种是在html中作标记, 如<button dojoType=dijit.form.Button label="button"></button>
关于其生成widget的机制, 我目前所知是它从原始DOM节点中获取信息, 如id, label, dojoType之类, 然后据此生成一个widget, 它本身应该是绑定了一个自己的DOM节点, 最后再将它替换原始DOM节点。

根据dijit.js,在创建一个widget过程中会调用如下几个函数
postMixInProperties: 刚刚从原始节点读入信息后调用, 如果扩展已有widget,可通过connect此函数来为widget对象添加新的属性(每个widget只从原始DOM节点(可用this.srcNodeRef得到其引用)中获取需要的节点, 其他的一概抛弃, 如果需要扩展, 要在销毁前得到它,而这里所列的几个函数中,仅有此函数执行时srcNodeRef尚未背销毁)
buildingRendering:刚刚为当前widget创建好DOM节点时调用
postCreate: 当前widget已创建好并放入UI后调用
startup: 所有widget都创建好后依次为每个widget调用此函数

尽管dijit提供了非常丰富的控件, 但也未必就能满足全部需要。 因此有时需要在现有控件基础上进行扩展。这时上面三个函数就显得非常有用。 不过需要注意的是, 用dojo.declare声明一个子类时, 不能直接重定义上面三个函数,否则父类的方法会被覆盖, 而且dojo似乎没有提供super之类的属性。 我的解决方法是用dojo.declare声明时仅添加新函数,而之后再用dojo.connect对已有函数进行扩展。 如果用此方法, 需要注意, 如果新类叫childClass, 那么应该connect childClass.prototype的方法, 我一开始忘了这一点, 结果怎么都不起作用。

五、其他
dojo与Firebug支持得非常好, 它能自动识别Firebug并与其交互, 再配合Firebug的强大功能, 开发javascript 轻松了很多

六、总结
通过几天的学习, 我对dojo库的基本思想和基本实现方法都有了些了解, 也十分欣赏和钦佩它的结构。它充分利用了javascript的灵活性, 创造了很多异想天开般的设计, 令我大开眼界
就先写这么多, 能些的也就是这么多了。 随着学习的深入, 我会再补充这个学习笔记的。

2 comments:

Alfred Wang said...

好文章,我最近也在学习dojo,受益了:)

一品凡心 said...

good article, but I still not found the function to substitude dom functions