JS控件的生命周期跟其他平台UI的生命周期類似,但是又有自己的特點,我們只有將控件的生命周期劃分清晰,所有的控件編寫、mixins的編寫和plugin的編寫才能遵循控件的生命周期做統一的管理。在這裡我把JS的生命周期定義為4部分:
1.initializer: 初始化,做一些不牽扯DOM操作的初始化操作
2.createDom: 創建 DOM,在這個過程中我們創建控件需要的DOM結構
3.renderUI: 生成控件的內部元素,在這裡調用子控件的渲染方法,開啟子控件的生命周期
4.bindUI: 綁定事件,可以綁定子控件事件也可以綁定內部DOM的事件
5.synUI: DOM結構以及子控件生成完畢後,我們在配置項中傳入的值或者默認的配置項要應用到DOM上,例如 width,height,focusable之類的屬性
6.destructor: 析構函數,移除控件,清理控件上的事件,清理子控件,清理控件自己的DOM以及控件的一些對其他控件的引用。
圖 1
初始化:
控件初始化過程中做以下事情:
1.調用繼承的父類的初始化函數,包括原型鏈上的父類和mixins
2.處理配置項,合並默認配置項和用戶傳入的配置項
3.處理綁定到改對象的事件
4.初始化插件(plugin)
初始化完成後,是否創建DOM看具體的策略,類似於ext的實現,可以延遲創建DOM
創建DOM
創建DOM的過程如下:
1.調用繼承的父類的創建DOM的函數,包括原型鏈上的父類和mixins
2.創建控件的DOM
3.調用控件插件的創建DOM的函數
渲染子控件和內部DOM操作
執行過程如下:
1.調用父類的渲染函數,包括原型鏈上的父類和mixins
2.調用插件的渲染函數
我們可以在頂級的父類來初始化子控件。好處是,子類不需要做子控件初始化的操作此時:
1.如果子控件還未初始化則執行初始化
2.繼續執行子控件的創建DOM、渲染子控件、綁定事件、同步配置項函數執行
綁定事件
由於此時控件的DOM和內部的子控件已經渲染完畢,則可以在子控件或者DOM上綁定事件。綁定事件的過程:
1.調用父類的綁定事件方法,包括原型鏈上的父類和mixins
2.調用插件(plugin)的綁定事件方式
注意:在子控件或者內部DOM上綁定事件時,使用委托,不要直接在子控件或者DOM上綁定事件,一旦子控件添加或者刪除,內部DOM變化都會引起事件失效。
同步配置項
首先說明一下什麼叫做同步配置項,前面我們在初始化控件時,已經對配置項做過一定的處理(至於如何處理,後面講 JS控件屬性 的時候會講到),但是配置項並未作用到DOM上或者內部子控件上。
為什麼在這時候處理同步,而不是在創建DOM和渲染子控件時,有2個原因:
1.在創建DOM和渲染子控件時,所有的DOM和子控件並未完整生成此時同步需要進行大量判斷
2.我們需要把同步配置項的工作提取成方法,修改配置項時,內部DOM和子控件跟著變化。
例如:配置項裡有 { width : 100 }
1.如果我們在渲染DOM時同步,則可能把“width=100px;”直接設置到DOM上,而到我們需要修改這個width時,我們還需要寫一個函數來設置這個值。
2.反之,我們把同步配置項集中處理,將一個個的同步配置項的過程抽取成一個個函數,那麼我們初始化 width的過程和修改width的過程完全一樣,這樣概念和邏輯就統一起來。
同步配置項的過程依然如其他步驟一樣:
1.調用父類的同步方法,包括原型鏈上的父類和mixins
2.調用插件(plugin)的同步方法
注意:我們應該可以配置一個配置項是否在此時同步,原因有很多,比如多個配置項會產生同一操作,如果多個配置項同時同步,那麼一個過程會反復執行多次。
移除控件
任何對象都有構造函數,必定也有析構函數,但是這個函數往往是大家最容易忽視的地方,但是也是非常重要的地方,暫且不說內存洩露之類的問題,就是如果一個控件的移除工作做得不夠好,會對正常的使用帶來很大的麻煩。
這個函數又是最不好寫的一個函數,因為它需要處理以下工作:
1.清理使用的其他控件,是否也移除看具體情形。區分 關聯和聚合
2.清理子控件
3.清理綁定到控件和DOM上的事件
4.移除DOM
5.清理變量的引用,這個比較麻煩和繁瑣,所以我們需要對控件的引用做統一的管理
同樣此函數也要執行:
1.調用父類的析構函數,包括原型鏈上的父類和mixins
2.調用插件的析構函數
問題
上面講的全部是具體的步驟,但是在實現的時候遇到了一系列的問題:
1.調用父類的方法存在問題
1)調用原型鏈上的父類方法,只能使用 className.superclass.method.call(this)這類的方法,this.constructor.superclass.method.call(this)不能使用,原因在 js 控件繼承 的extend一章中有講到,這種調用方式繁瑣而且維護不方便。
2)調用mixins上的方法,在JS 繼承 mixins一章中我講到過mixins的實現原理,覆蓋同名方法,mixins的方法其實已經作為控件的prototype上的方法,所以最好不要使用同名方法,如果多個mixins都是用 renderUI,synUI之類的方法,而繼承這些mixins的控件沒有實現renderUI這類方法,那麼就會被覆蓋。
2.調用插件的方法也存在問題
1)我們需要獲得當前控件的引用
解決方式:
1. 針對調用父類的方法我們可以在控件渲染時,按照原型鏈的順序,先調用父類的方法再調用子類的方法直到當前控件:
如上面的繼承關系,我們執行C.renderUI()時,按照繼承原型鏈的頂層向下執行。
2. 執行minxins的方法,我們執行renderUI時,去依次執行 mixin 的__renderUI方法。
3. 執行父類的renderUI 時,如果也存在mixins那麼執行mixin的 __renderUI方法。
圖3
如上圖所示: inherits表示原型鏈繼承,extends表示 mixin擴展
那麼c.renderUI的執行過程如下:
A.renderUI ->D.__renderUI->B.renderUI->E.__renderUI->C.renderUI
3. 調用插件方法時需要傳遞控件本身的引用即可,如:
plugin1.render(this)
4. 析構函數 destructor比較特使,執行的順序跟上面2中講的順序有所不同:
子類 destructor -> 子類擴展 destructor -> 父類 destructor -> 父類擴展 destructor
按照圖3的繼承結構其析構函數執行的順序是:
C. destructor ->E.__destructor->B.destructor->E.__destructor->a.destructor
原因是,子類的一些引用依賴於父類或者擴展類,如果父類和擴展類先執行析構函數,那麼子類在使用某些變量/屬性時會報錯。
這一節我把控件的生命周期講了一遍,所有的著一些都是來自於 KISSY框架的UIBase,感興趣的可以去看一下,非常精彩的實現http://docs.kissyui.com/kissy/docs/#!/api/KISSY.Component.UIBase的