转自:http://blog.csdn.net/quincylk/article/details/17613563
在使用dojo过程中对于组件生命周期一直不是特别清楚,官方文档写的也不够恰当,所以特地研究了一下,做了公司内部培训,这就是为什么有了这篇文章。本文主要通过读DOJO的源码以及源码中的注释来了解DOJO组件的生命周期。今天分享给大家,供大家参考,不当之处欢迎大家指教。
1.组件生命周期预览
dojo组件生命周期比较重要的是以下几个部分
调用constructor方法
将创建组件时指定的参数mixin到组件实例中
执行postMixInProperties方法
执行buildRendering方法(创建组件的DOM对象)
复制组件对象中的属性到DOM对象中
执行postCreate方法
执行startup方法
组件销毁时执行destroy方法
注:
constructor方法在new后者parse时调用。
startup方法可由外层组件或者placeAt或者addChild再或者手动调用。
其他的方法均是在组件的create方法中调用。
2.各方法作用及调用时机
2.1 constructor
创建一个组件对象时调用,可以做一些变量初始化.值得注意的是此时传递给组件的变量还没有mixin到组件对象中。
2.2 postMixInProperties
在执行postMixInProperties方法之前传递给组件对象的属性已经mixin到组件对象中,可以在create方法中找到如下代码:
// mix in our passed parameters
if (params) {
this.params = params;
lang.mixin(this, params);
}
this.postMixInProperties();
但是此时组件的DOM对象还没有创建,如果想在DOM对象创建之前修改组件对象的属性在这个方法中做是适合的。但是值得注意的一点在执行完buildRendering之后且在执行postCreate方法之前会执行_applyAttributes方法,而_applyAttributes方法中会将创建组件对象时传递的参数再一次覆盖到组件对象中(在执行postMixinProperties方法前已经覆盖过一次), 所以在执行_applyAttributes之前做的相关属性修改都会失效。
//create方法中片段
this.buildRendering();
var deleteSrcNodeRef;
if (this.domNode) {
this._applyAttributes();
var source = this.srcNodeRef;
if (source && source.parentNode && this.domNode !== source) {
source.parentNode.replaceChild(this.domNode, source);
deleteSrcNodeRef = true;
}
this.domNode.setAttribute("widgetId", this.id);
}
this.postCreate();
//_applyAttributes代码片段
for (var param in this.params) {
this.set(param, this.params[param]);
}
注意:虽然在postMixinProperties方法执行前已经将创建组件时配置对象中的属性mixin到组件对象中,但是对这些属性进行更改是不恰当的,dojo会在执行完成postMixinProperties后做第二次属性覆盖。所以建议在postCreate中进行修改。
2.3 buildRendering
这个方法的作用是创建组件的DOM对象,并将DOM对象的指针保存在this.domNode中。先来看一下执行完postMixInProperties方法且在执行buildRendering方法之前做了什么:
1)如果组件对象没有id则根据组件类名字(带命名空间)生成组件id。生成规则是将类名字中的点(.)替换为下划线(_),并根据当前实例是组件类的第几个实例生成相应的下标,id类似于dojox_slickgrid_slickgrid_0。如果声明组件时指定了DOM的id则组件的id使用DOM的id。id优先级规则为创建组件时指定的id > 组件创建锚点的dom id > 自动生成的id。
2)将组件注册到dijit/registry模块中(registry.add(this);),这就是为什么能用registry.byId('组件id')获取到组件。
言归正传,接下来接着说buildRendering。buildRendering创建DOM对象分以下几种情况:
1)有模板。一般来说在有模板的组件都会mixin _TemplatedMixin模块,该模块重写了buildRendering方法,重写方法利用模板生成DOM对象。
2)没有模板,声明式创建或者程序式创建指定了组件放置的DOM,如new Grid({}, "grid-idv")中的第二参数即为放置组件的锚点。此时将声明组件的DOM对象作为this,domNode。
3)没有模板,没有指定放置组件的DOM。此时生成一个DIV DOM对象作为this.domNode。
注:buildRendering方法的另一个作用是将组件对象中的baseClass追加给this.domNode。
模板中的data-dojo-attach-point='containerNode'有什么特殊用处?
当声明式创建有模板的组件时开始标签和结束标签中间包含的内容会自动包含进以'containerNode'为附着点的标签内,而且内容可以dojo组件。
在什么地方完成的上述操作?
在_TemplatedMixin的buildRendering中。
模板中的containerNode存在其他内容会怎样?
如果模板中存在其他内容,声明式开始标签和结束标签之间的内容将追加在containerNode模板内容之后。
特别说明:
_TemplatedMixin模块中的buildRendering方法可以缓存模板生成的DOM节点,但有两个前提条件,如下:
a)组件对象的全局属性_skipNodeCache为false。
b)模板中没有模板变量。
另外如果用templatePath指定模板而不是用templateString指定模板,_TemplatedMixin方法会将templatePath指定的模板缓存在templateString中。
2.4 postCreate
这是一个常用的组件扩展点,执行这个方法的时候this.domNode已经被创建了,这个时候做事件绑定是合适的。虽然组件的DOM对象已经创建了,但是做尺寸相关的计算是有风险的,原因是执行该方法的时候this.domNode并不一定被添加到文档树中,外联样式不会对该DOM生效。
在执行postCreate之前会做如下操作:
1)将组件对象中的属性复制到this.domNode对象中
2)如果是声明式创建或者程序式创建并且指定了组件要放置的DOM描点则将this.domNode添加到文档中,如果程序式不指定组件要放置的DOM则不添加到文档中(这就是在postCreate中做尺寸计算存在风险的原因)。示例代码依次如
//声明式:
<div data-dojo-type="dijit.layout.ContentPane"></div>
//程序式指定组件要放置的DOM:
//js
new Grid({},'grid-div');
//html
<div id="grid-div"></div>
//程序式不指定组件要放置的DOM
//js
var panel = new ContentPane({});
panel.palceAt("xx-dom")
2.5 startup
这个方法一般对于layout组件特别有用,看官方文档:
If you need to be sure parsing and creation of any child widgets has completed, use startup. This is often used for layout widgets like BorderContainer. If the widget does JS sizing, then startup() should call resize(), which does the sizing.
startup方法的调用规则:
1)组件包含在其他容器组件之内(包括程序式和声明式,这也是为什么完全程序式创建的项目最外层布局组件一定要手动调用startup的原因),startup方法会由外层容器组件调用。可以在_WidgetBase和_WidgetInTemplateMixin中的startup中窥视到这一点,如下:
//_WidgetBasestartup方法
startup : function () {
// summary:
//Processing after the DOM fragment is added to the document
// description:
//Called after a widget and its children have been created and added to the page,
//and all related widgets have finished their create() cycle, up through postCreate().
//This is useful for composite widgets that need to control or layout sub-widgets.
//Many layout widgets can use this as a wiring phase.
if (this._started) {
return;
}
this._started = true;
array.forEach(this.getChildren(), function (obj) {
if (!obj._started && !obj._destroyed && lang.isFunction(obj.startup)) {
obj.startup();
obj._started = true;
}
});
}
//WidgetInTemplateMixin方法中的startup方法
startup : function () {
array.forEach(this._startupWidgets, function (w) {
if (w && !w._started && w.startup) {
w.startup();
}
});
this.inherited(arguments);
}
需要注意的是如下的写法widget组件是包含在容器组件ContentPane中的,所以最外层的ContentPane会调用内层的组件的startup。
<div data-dojo-type="dijit.layout.ContentPane">
<div>
<a>
<div id="c2" data-dojo-type="dijit.layout.ContentPane">
<div data-dojo-type="demos.slickgrid.widget"></div>
</div>
</a>
</div>
</div>
2)组件不包含在任何容器组件之内(如,直接放在body内),此时startup方法会由parser调用(声明式)。
3)程序式创建不包含在其他组件之内的时候可以由以下几种方式调用:
A)手动调用。
B)将组件placeAt到指定DOM对象时placeAt会调用startup方法。但是这样情况是有条件的,首先是startup没有被调用过,其次是placeAt后组件有父组件(父组件必须先于子组件创建),再次父组件的startup已经调用过。
//placeAt中代码片段
// Start this iff it has a parent widget that's already started.
if (!this._started && (this.getParent() || {})._started) {
this.startup();
}
C)容器组件调用自己的addChild方法将组件添加到自己之中是会调用子组件的startup。
if (this._started && !widget._started) {
widget.startup();
}
注:addChild与placeAt方法功能类似,addChild必须是容器组件,placeAt可以把组件放在任何DOM对象内。addChild和placeAt均可接收第二参数,即组件放在容器组件中的位置。
2.6非生命周期的重要方法resize
所有做js尺寸计算的组件都应该实现resize方法,官方文档如下:
All widgets that do JS sizing should have a method called resize(), that lays out the widget. Resize() should be called from startup() and will also be called by parent widgets like dijit.layout.ContentPane.
The resize function for widgets layout widgets serves two purposes:
set the size of the widget
make the widget adjust the size of its children
resize children recursively
看一下_ContentPaneResizeMixin是怎么调用子组件的resize方法的,如下:
_layoutChildren : function () {
if (this.doLayout) {
this._checkIfSingleChild();
}
if (this._singleChild && this._singleChild.resize) {
var cb = this._contentBox || domGeometry.getContentBox(this.containerNode);
this._singleChild.resize({
w : cb.w,
h : cb.h
});
} else {
array.forEach(this.getChildren(), function (widget) {
if (widget.resize) {
widget.resize();
}
});
}
}
2.7维护继承链的重要方法inherited
在绝大部分时候是有必要在重写方法中调用父类中的方法,掉用的形式是this.inherited(arguments),比如_WidgetInTemplateMixin中的startup最后一句执行this.inherited(arguments) (见2.5)。
2.8程序式创建应用与生命周期及最佳实践
1、Creation。程序式创建应用的时候一般先创建外层容器,之后向容器中add子组件,最后调用最外层容器组件的starttup。
注意:
A)startup方法仅仅在最外层子组件调用一次就好了。
B)如果可能,最后调用startup方法,即在子组件添加到容器组件后。
C)在调用startup方法前顶层组件必须已经添加到document中,这样节点才能正确的获取到尺寸。
D)最外层组件也许需要指定尺寸,里层组件通常不需要指定尺寸(bordercontainer的各区域除外),因为他们的尺寸可以被父组件侦测调整。
2、Add Children
执行完startup之后仍然可以通过容器组件的addChild方法添加子组件,且addChild会调用子组件的startup。
3、Remove Children
容器组件的removeChild方法用于解除容器组件与子组件的关系,这不是销毁子组件。
4、Destruction
销毁容器组件和及其子组件典型的方法是调用容器组件的destroyRecursize方法。
3.dijit/_WidgetBase类提供的其他功能
3.1set、 get、 watch
分别用于设置、获取组件属性和监听指定属性的变化,举例如下:
var w = new widget({}, 'widget');
w.startup();
w.watch('p', function (property, oldValue, newValue) {
console.log(property + " -- " + oldValue + " -- " + newValue);
});
w.set("p", 'new');
3.2emit和on
分别是在组件上发布事件和绑定事件,举例如下:
//发布事件
myWidget.emit("attrmodified-selectedChildWidget", {});
//绑定事件
myWidget.on("click", function () {
//...
});
4.参考资料:
1.DOJO的源码以及源码中的注释
2.参考的官方文档主要为如下两篇:
http://dojotoolkit.org/reference-guide/1.8/quickstart/writingWidgets.html
http://dojotoolkit.org/reference-guide/1.8/dijit/_WidgetBase.html
3.参考CSDN上一篇文章:
http://www.voidcn.com/article/p-tyxrueds-tz.html