JavaScript 生命週期

一、JavaScript有生命週期嗎?

轉自:http://blog.sina.com.cn/s/blog_694c144f0101pida.html

從頁面數據被裝載到頁面數據裝載完畢的初始化階段:javascript代碼被瀏覽器解析,運行環境被初始化,函數和閉包被建立,而那些可以被立即執行的指令被執行並實時地得到結果。

頁面數據裝載完畢一直到新的請求被髮起之前的運行階段:完成初始化的程序環境進入一個缺省的等待消息的循環,捕獲用戶操作引發的事件並作出正確響應,這種模式同經典的事件驅動模型非常接近。在這一階段裏,javascrip代碼真正扮演一個界面交互行爲處理者的角色。


被臃腫頁面修飾的javascrip代碼通常在初始化階段被執行完畢,而負責用戶交互的javascript幾乎總是要在運行階段被觸發和執行。區分這兩者的作用和執行規律,有助於分解問題,優化我們的系統設計。

第二種方式中,指令不會對裝載期的文檔內容產生影響,腳本指令被註冊到文檔元素加載完成後的某些元素的事件中執行,這樣確保了在執行前所有的文檔元素都已經正確初始化完畢。
假如出現某種意外導致程序終止,第一個示例可能因此而導致文檔數據不能加載完全,而後者則不會有這樣的風險。

一個比較好的習慣是把除聲明之外的所有腳本指令都放到運行階段來執行,這樣避免了因爲初始化期間的DOM元素加載失敗或者低級的次序問題而導致腳本失效。

二、JavaScript變量的生命週期

JavaScript與其他的Java或C等語言不同

在JavaScript中,對於for循環中定義的i變量,其生命週期在循環結束後仍然是有效的。

<html>
    <head></head>
    <body>
        <script type="text/javascript">
            var global_one = "I am global";
            function fun(){
                global_two = "I am global too";
                var local_one = "I am local";
            }
            alert(global_one); // "I am global"
            //alert(global_two);// error
            //alert(local_one);//error
 
            fun();
            alert(global_one); // "I am global"
            alert(global_two);// "I am global"
            //alert(local_one);//error
        </script>
    </body>
</html>

JavaScript中的方法內部定義變量的時候如果沒有加var,就是全局變量;否則爲局部變量;
當fun()沒有執行的時候,方法內部的全局變量是不會聲明並且定義的。

三、JavaScript函數的生命週期

JavaScript函數的生命週期分爲創建和調用。

有一個疑問:所有函數聲明都是在預編譯階段實現的。當然,由於作用域的限制,內層函數聲明沒法直接在外部呈現。變量名定義和函數聲明都是在預編譯階段完成的工作,不可能讓編譯器進入執行階段後再退回預編譯階段去完成函數聲明。

四、JS控件的生命週期介紹

轉自:http://www.jb51.net/article/31611.htm

JS控件的生命週期跟其他平臺UI的生命週期類似,但是又有自己的特點。在這裏我把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.調用繼承的父類的初始化函數,包括原型鏈上的父類和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
原因是,子類的一些引用依賴於父類或者擴展類,如果父類和擴展類先執行析構函數,那麼子類在使用某些變量/屬性時會報錯。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章