對於一個在前端屬於純新手的我來說,Javascript都還是一知半解,要想直接上手angular JS,遇到的阻力還真是不少。不過我相信,只要下功夫,即使是反人類的設計也不是什麼大的問題。
Okay,也不多說廢話。為了弄清楚angular JS為何物,我先從Scope開始。那什麼是Scope呢?借用官方文件的一段話:
Scope的特性
接下來,看看Scope有哪些特性呢?Scope提供$watch方法監視Model的變化。
Scope提供$apply方法傳播Model的變化。
Scope可以繼承,用來隔離不同的application components和屬性存取權。
Scope為Expressions的計算提供上下文。
源起Javascript
首先,乍一看,scope.apply()似乎就是一個使得bindings得到更新的普通通的方法。但稍微多想一點,為什麼我們需要它?一般什麼時候用它呢?用弄清楚這兩個問題,還得從Javascript說。在Javascript程式碼裡,都是按照一定順序來執行的,當輪到一個程式碼片段執行的時候,瀏覽器就只會去執行目前的片段,不會做任何其他的事情。所以有時候一些做得不是很好的網頁,當點擊了某個東西之後會卡住,Javascript的工作方式就是會導致這一現象原因之一!下面我們有一段程式碼可以感受一下:
當載入Javascript程式碼時,先找一個一個id叫「clickMe」的按鈕,然後加入一個監聽器,然後設定逾時。等待5秒,會跳出一個對話框。如果刷新頁面並立即點擊clickMe按鈕,會彈出一個對話框,如果你不點擊OK,timerComplete函數永遠沒有機會執行。
如何更新bindings
好了,扯了一些看似不相關的東西之後,我們回歸正題。 angular JS是怎麼知道什麼時候資料的變化和頁面需要更新的呢?程式碼需要知道什麼時候資料被修改了,但是現在又沒有一種方法直接去通知說某個物件上的資料變了(儘管ECMAScript 5正在嘗試解決這個問題,但也還是處於實驗階段)。而目前比較主流的策略有以下有兩種解決方案。一種是需要用特殊的對象,讓所有的資料都只能透過呼叫對象的方法來設置,而不是直接透過property指定。這樣的話,所有的修改就可以被記錄下來了,就知道什麼時候頁面需要更新了。這樣做的弊端就是我們必須去繼承一個特殊的物件。對於賦值也只能透過object.set('key', 'value')而不是object.key=value的方式。在框架中,像EmberJS和KnockoutJS就是這麼幹的(雖然我都沒接觸過-囧)。另一種就是angular JS採用的方式,每一次Javascript程式碼執行序列執行結束後都會去檢查是否有資料的改變。這看起來似乎不高效,甚至嚴重影響性能。但是angular JS採用了一些比較巧妙的手段解決了這個問題(還沒研究過,目前尚不清楚)。這麼做的好處就是,我們可以隨便使用任意對象,對於賦值方式也沒有限制,而且對於資料的改變也能覺察。
對於angular JS所採取的這種解決方案,我們關心的是何時資料發生了變化,而這也正是scope.apply()派上用場的地方。對於檢查綁定的資料到底有沒有發生變化,實際上是由scope.digest()完成的,但是我們幾乎從來就沒有直接呼叫過這個方法,而是呼叫scope.apply()方法,是因為在scope .apply()方法裡面,它會去呼叫scope.digest()方法。 scope.apply()方法帶一個函數或一個表達式,然後執行它,最後呼叫scope.digest()方法去更新bindings或watchers。
何時用$apply()
還是那個問題,那我們到底什麼時候需要去呼叫apply()方法呢?情況非常少,實際上幾乎我們所有的程式碼都包在scope.apply()裡面,像ng−click,controller的初始化,http的回調函數等。在這些情況下,我們不需要自己調用,實際上我們也不能自己調用,否則在apply()方法裡面再調用apply()方法會拋出錯誤。如果我們需要在一個新的執行序列中運行程式碼時才真正需要用到它,而且當且僅當這個新的執行序列不是被angular JS的庫的方法創建的,這個時候我們需要將程式碼用scope. apply()包起來。下面用一個例子解釋:
上面的程式碼執行後頁面上會顯示:Waiting 2000ms for update。顯然數據的更新並沒有被angular JS覺察到。
接下來,我們將Javascript的程式碼稍作修改,並用scope.apply()包起來。
這次與之前不同的是,頁面上先會顯示:Waiting 2000ms for update,等待2秒後內容會被更改為:Timeout called! 。顯然數據的更新被angular JS覺察到了。
NOTE:我們不應該這樣做,而是用angular JS提供的timeout方法,這樣它就會被自動用apply方法包起來了。
科學是把雙面刃
最後,我們再來瞅一眼scope.apply()和scope.apply(function)方法!雖然angular JS為我們做了很多事情,但我們也因此失去了一些機會。從下面的偽代碼一看便知:
它會捕捉所有的例外並且不會再拋出來,最後都會呼叫$digest()方法。
總結一下
$apply()方法可以在angular框架之外執行angular JS的表達式,例如:DOM事件、setTimeout、XHR或其他第三方的函式庫。這只是個開始,水還有很深,歡迎大家一起來deep dive!