為什麼你的 Angular 雙向資料繫結會失效?

亞里士朱德發表於2017-06-28

Angular雙向資料繫結原理探究。

文章原始碼引用較多,覺得難以理解可以直接跳到末尾總結處。

接觸過Angular的人一定會對其“雙向資料繫結”的特性印象深刻,而使用過的人更會對莫名其妙出現的雙向資料繫結失效的“坑”所困擾。例如下面一段程式碼:

原始碼地址:http://jsbin.com/xogosim/edit?html,js,console,output

如果上面程式碼中的兩個問題你都知道答案,那麼你可以跳過下面的內容,如果並不完全清楚,那麼我們接著往下說~

雙向資料繫結,指的是檢視和模型之間的對映關係。雙向即 檢視 ==> 模型模型 ==> 檢視 兩個方向。

我們以Angular1.3為例,探究一下這個問題。

檢視 ==> 模型

拋開Angular不說,如果我們要實現檢視修改時觸發模型的修改,很簡單,事件(鍵盤事件、滑鼠事件、UI事件)監聽就能實現。而Angular會不會也是這麼實現的?

最常用的場景便是表單元素的資料繫結,當元素的值發生變化時我們要通知模型層(比如校驗、聯動),例如用於實現這一功能的 ngModel 指令。

但是我們如果直接找到ngModel的原始碼,並沒有找到直接的事件繫結,依賴ngModelOptions指令倒是有一段程式碼繫結了事件

可是平常沒使用ngModelOptions的時候也能同步元素的修改,難道是一開始就想錯了?

回憶一下Angular定義指令的時候,不光有像ngModel這樣通過屬性定義,也有直接定義成元素的,例如form就是一個指令。而最常用最簡單的就是把ngModel用在input元素上,不,應該是input指令。

於是找到input指令的程式碼

發現只要nhgModel指令存在的時候,它就會根據type屬性執行一段函式。

我們找到inputType.text這個函式之後,層層追尋…

終於找到了它在繫結事件的證據,而且還很智慧,根據瀏覽器對事件的支援情況來進行繫結。

發現繫結的事件都執行了一個函式:$setViewValue。繼續查詢,發現呼叫ngModelSet函式來修改模型。

模型 ==> 檢視

我們再次拋開Angular,回到原生實現,如果我們想要修改檢視也比較簡單,獲取dom元素並修改對應的屬性。

再找一個在Angular中將模型值同步到dom上的指令ngBind

發現其在scope.$watch回撥函式中來修改dom元素的文字內容。那我們可以大膽地推測,應該是在修改了對應的$scope屬性值之後,觸發了scope.$watch呼叫了ngBindWatchAction回撥函式才導致頁面元素文字變化的。

從原始碼中可以看到,當我們在呼叫$watch監控變數的時候,其實是建立了一個watcher物件,並將其放入$scope.$$watchers陣列中。

那麼誰會用到這個陣列,並且其中的回撥函式呢?

這個程式碼有點難找,直到找到一個叫做$digest的函式定義。

簡單概括一下這段程式碼,遍歷$scope.$$watchers,判斷如果需要檢測的表示式的值(可以理解為$scope的屬性)發生了修改,那麼執行對應回撥函式(比如ngBindg中的ngBindWatchAction)。

修改$scope對應的屬性,並呼叫$scope.$digest。完成這兩個條件即可同步模型資料到檢視,修改dom元素。換句話說,這兩個條件缺一不可。而呼叫$scope.digest這一過程,我們一般叫做髒值檢測

有人可能會說我呼叫$scope.$apply也可以啊~

理論上來說,用$scope.$digest完成的手動試圖同步都可以用$scope.$apply,但是他們之間還是有區別。

區別就在於,$apply是對$rootScope及子作用域做髒值檢測,意味著效能消耗更大。支援回掉函式算是一個好處。

總結

檢視 ==事件繫結==> 模型

模型 <==髒值檢測== 模型

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

為什麼你的 Angular 雙向資料繫結會失效? 為什麼你的 Angular 雙向資料繫結會失效?

相關文章