對(duì)于一個(gè)在前端屬于純新手的我來(lái)說(shuō),Javascript都還是一知半解,要想直接上手angular JS,遇到的阻力還真是不少。不過(guò)我相信,只要下功夫,即使是反人類的設(shè)計(jì)也不是什么大的問(wèn)題。
Okay,廢話不多說(shuō)。為了弄明白angular JS為何物,我先是從Scope開(kāi)始。那么什么是Scope呢?借用官方文檔的一段話:
看完后,類比到其他的編程語(yǔ)言上,感覺(jué)Scope就像是Data Model的作用域一樣,為Expressions的執(zhí)行提供上下文,暫且先這么理解吧。
Scope的特性
接下來(lái),看看Scope有哪些特性呢?
Scope提供$watch方法監(jiān)視Model的變化。
Scope提供$apply方法傳播Model的變化。
Scope可以繼承,用來(lái)隔離不同的application components和屬性訪問(wèn)權(quán)限。
Scope為Expressions的計(jì)算提供上下文。
對(duì)于這四點(diǎn)特性,因?yàn)槲抑皩W(xué)習(xí)過(guò)ActionScript、C++、Java,所以第一、三、四點(diǎn)不難理解,唯獨(dú)第二點(diǎn)感覺(jué)有點(diǎn)云里霧里。本著打破沙鍋問(wèn)到底的原則,我還是通過(guò)Google搜到了一些東西。對(duì)于有經(jīng)驗(yàn)的老手,板磚請(qǐng)輕拍!
源起Javascript
首先,乍一看,scope.apply()似乎就是一個(gè)使得bindings得到更新的普普通通的方法。但稍微多想一點(diǎn),為什么我們需要它?一般在什么時(shí)候用它呢?用弄明白這兩個(gè)問(wèn)題,還得從Javascript說(shuō)起。在Javascript代碼里,都是按照一定順序來(lái)執(zhí)行的,當(dāng)輪到一個(gè)代碼片段執(zhí)行的時(shí)候,瀏覽器就只會(huì)去執(zhí)行當(dāng)前的片段,不會(huì)做任何其他的事情。所以有時(shí)候一些做得不是很好的網(wǎng)頁(yè),當(dāng)點(diǎn)擊了某個(gè)東西之后會(huì)卡住,Javascript的工作方式就是會(huì)導(dǎo)致這一現(xiàn)象原因之一!下面我們有一段代碼來(lái)感受一下:
當(dāng)加載Javascript代碼時(shí),先先找一個(gè)一個(gè)id叫“clickMe”的按鈕,然后添加一個(gè)監(jiān)聽(tīng)器,然后設(shè)置超時(shí)。等待5秒,會(huì)彈出一個(gè)對(duì)話框。如果刷新頁(yè)面并立即點(diǎn)擊clickMe按鈕,會(huì)彈出一個(gè)對(duì)話框,如果你不點(diǎn)擊OK,timerComplete函數(shù)永遠(yuǎn)沒(méi)有機(jī)會(huì)執(zhí)行。
如何更新bindings
好了,扯了一些看似不相關(guān)的東西之后,我們回歸正題。angular JS是怎么知道什么時(shí)候數(shù)據(jù)的變化和頁(yè)面需要更新的呢?代碼需要知道什么時(shí)候數(shù)據(jù)被修改了,但是現(xiàn)在又沒(méi)有一種方法直接去通知說(shuō)某個(gè)對(duì)象上的數(shù)據(jù)變了(盡管ECMAScript 5正在嘗試解決這一問(wèn)題,但也還是處于實(shí)驗(yàn)階段)。而目前比較主流的策略有以下有兩種解決方案。一種是需要用特殊的對(duì)象,讓所有的數(shù)據(jù)都只能通過(guò)調(diào)用對(duì)象的方法設(shè)置,而不是直接通過(guò)property指定。這樣的話,所有的修改就可以被記錄下來(lái)了,就知道什么時(shí)候頁(yè)面需要更新了。這樣做的弊端就是我們必須去繼承一個(gè)特殊的對(duì)象。對(duì)于賦值也只能通過(guò)object.set('key', 'value')而不是object.key=value的方式。在框架中,像EmberJS和KnockoutJS就是這么干的(雖然我都沒(méi)接觸過(guò)——囧)。另一種就是angular JS采用的方式,在每一次Javascript代碼執(zhí)行序列執(zhí)行結(jié)束后都去檢查是否有數(shù)據(jù)的改變。這看起來(lái)似乎并不高效,甚至嚴(yán)重影響性能。但是angular JS采用了一些比較巧妙的手段解決了這個(gè)問(wèn)題(還沒(méi)研究過(guò),目前尚不明確)。這么做的好處就是,我們可以隨便使用任意對(duì)象,對(duì)于賦值方式也沒(méi)有限制,而且對(duì)于數(shù)據(jù)的改變也能覺(jué)察到。
對(duì)于angular JS采取的這種解決方案,我們關(guān)心的是什么時(shí)候數(shù)據(jù)發(fā)生了變化,而這也正是scope.apply()派上用場(chǎng)的地方。對(duì)于檢查綁定的數(shù)據(jù)到底有沒(méi)有發(fā)生變化,實(shí)際上是由scope.digest()完成的,但是我們幾乎從來(lái)就沒(méi)有直接調(diào)用過(guò)這個(gè)方法,而是調(diào)用scope.apply()方法,是因?yàn)樵趕cope.apply()方法里面,它會(huì)去調(diào)用scope.digest()方法。scope.apply()方法帶一個(gè)函數(shù)或者一個(gè)表達(dá)式,然后執(zhí)行它,最后調(diào)用scope.digest()方法去更新bindings或者watchers。
什么時(shí)候用$apply()
還是那個(gè)問(wèn)題,那我們到底什么時(shí)候需要去調(diào)用apply()方法呢?情況非常少,實(shí)際上幾乎我們所有的代碼都包在scope.apply()里面,像ng−click,controller的初始化,http的回調(diào)函數(shù)等。在這些情況下,我們不需要自己調(diào)用,實(shí)際上我們也不能自己調(diào)用,否則在apply()方法里面再調(diào)用apply()方法會(huì)拋出錯(cuò)誤。如果我們需要在一個(gè)新的執(zhí)行序列中運(yùn)行代碼時(shí)才真正需要用到它,而且當(dāng)且僅當(dāng)這個(gè)新的執(zhí)行序列不是被angular JS的庫(kù)的方法創(chuàng)建的,這個(gè)時(shí)候我們需要將代碼用scope.apply()包起來(lái)。下面用一個(gè)例子解釋:
上面的代碼執(zhí)行后頁(yè)面上會(huì)顯示:Waiting 2000ms for update。顯然數(shù)據(jù)的更新沒(méi)有被angular JS覺(jué)察到。
接下來(lái),我們將Javascript的代碼稍作修改,用scope.apply()包起來(lái)。
這次與之前不同的是,頁(yè)面上先會(huì)顯示:Waiting 2000ms for update,等待2秒后內(nèi)容會(huì)被更改為:Timeout called! 。顯然數(shù)據(jù)的更新被angular JS覺(jué)察到了。
NOTE:我們不應(yīng)該這樣做,而是用angular JS提供的timeout方法,這樣它就會(huì)被自動(dòng)用apply方法包起來(lái)了。
科學(xué)是把雙刃劍
最后,我們?cè)賮?lái)瞅一眼scope.apply()和scope.apply(function)方法吧!雖然angular JS為我們做了很多事情,但是我們也因此丟失了一些機(jī)會(huì)。從下面的偽代碼一看便知:
它會(huì)捕獲所有的異常并且不會(huì)再拋出來(lái),最后都會(huì)調(diào)用$digest()方法。
總結(jié)一下
$apply()方法可以在angular框架之外執(zhí)行angular JS的表達(dá)式,例如:DOM事件、setTimeout、XHR或其他第三方的庫(kù)。這僅僅是個(gè)開(kāi)始,水還有很深,歡迎大家一起來(lái)deep dive!
新聞熱點(diǎn)
疑難解答
圖片精選