欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解angular臟檢查原理及偽代碼實現(xiàn)

 更新時間:2018年06月08日 16:54:39   作者:否子戈  
這篇文章主要介紹了詳解angular臟檢查原理及偽代碼實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

我們經(jīng)常聽到angular的臟檢查機(jī)制和數(shù)據(jù)的雙向綁定,這兩個詞似乎已經(jīng)是它的代名詞了。那么從編程層面,這到底是什么鬼?

當(dāng)$scope的一個屬性被改變時,界面可能會更新。那么為什么angular里面,修改$scope上的一個屬性,可以引起界面的變化呢?這是angular的數(shù)據(jù)響應(yīng)機(jī)制決定的。在angular里面就是臟檢查機(jī)制。而臟檢查,和雙向綁定離不開。

這里插句題外話,JavaScript里面非常有意思的一種接口,當(dāng)你修改(或新增)一個對象的某個屬性時,會觸發(fā)該對象里面的setter。如果你對這塊不是很了解,可以先學(xué)一下 Object.defineProperty ,包括這兩年超級火的vuejs也是通過這個接口實現(xiàn)的。它是一個ES5的標(biāo)準(zhǔn)接口。

我們可以設(shè)計一種實現(xiàn),當(dāng)你修改或賦值$scope的某個屬性時,就觸發(fā)了$scope這個js對象的setter,我們可以自定義這個setter,在setter函數(shù)內(nèi)部,調(diào)用某些邏輯去更新界面。同時,為了確保新塞進(jìn)來的對象也可以被監(jiān)聽到變化,在你賦值時,還要把賦值進(jìn)來的對象也進(jìn)行改造,改造為可以被監(jiān)聽的對象。

雙向綁定顧名思義是兩個過程,一個是將$scope屬性值綁定到HTML結(jié)構(gòu)中,當(dāng)$scope屬性值發(fā)生變化的時候界面也發(fā)生變化;另一個是,當(dāng)用戶在界面上進(jìn)行操作,例如點擊、輸入、選擇時,自動觸發(fā)$scope屬性的變化(界面也可能跟著變)。而臟檢查的作用是“在當(dāng)$scope屬性值發(fā)生變化的時候促使界面發(fā)生變化”。

angular的數(shù)據(jù)響應(yīng)機(jī)制

那么,在代碼層面,angular是怎么做到監(jiān)聽數(shù)據(jù)變動然后更新界面的呢?答案是,angular根本不監(jiān)聽數(shù)據(jù)的變動,而是在恰當(dāng)?shù)臅r機(jī)從$rootScope開始遍歷所有$scope,檢查它們上面的屬性值是否有變化,如果有變化,就用一個變量dirty記錄為true,再次進(jìn)行遍歷,如此往復(fù),直到某一個遍歷完成時,這些$scope的屬性值都沒有變化時,結(jié)束遍歷。由于使用了一個dirty變量作為記錄,因此被稱為臟檢查機(jī)制。

這里面有三個問題:

  1. “恰當(dāng)?shù)臅r機(jī)”是什么時候?
  2. 如何做到知道屬性值是否有變化?
  3. 這個遍歷循環(huán)是怎么實現(xiàn)的?

要解決這三個問題,我們需要深入了解angular的$watch, $apply, $digest。

$watch綁定要檢查的值

簡單的說,當(dāng)一個作用域創(chuàng)建的時候,angular會去解析模板中當(dāng)前作用域下的模板結(jié)構(gòu),并且自動將那些插值(如{{text}})或調(diào)用(如ng-click="update")找出來,并利用$watch建立綁定,它的回調(diào)函數(shù)用于決定如果新值和舊值不同時(或相同時)要干什么事。當(dāng)然,你也可以手動在腳本里面使用$scope.$watch對某個屬性進(jìn)行綁定。它的使用方法如下:

$scope.$watch(string|function, listener, objectEquality, prettyPrintExpression)

第一個參數(shù)是一個字符串或函數(shù),如果是函數(shù),需要運(yùn)行后得到一個字符串,這個字符串用于確定將綁定$scope上的哪個屬性。listener則是回調(diào)函數(shù),表示當(dāng)這個屬性的值發(fā)生變化時,執(zhí)行該函數(shù)。objectEquality是一個boolean,為true的時候,會對object進(jìn)行深檢查(懂什么叫深拷貝的話就懂深檢查)。第四個參數(shù)是如何解析第一個參數(shù)的表達(dá)式,使用比較復(fù)雜,一般不傳。

$digest遍歷遞歸

當(dāng)使用$watch綁定了要檢查的屬性之后,當(dāng)這個屬性發(fā)生變化,就會執(zhí)行回調(diào)函數(shù)。但是前面已經(jīng)說過了,angular里面沒有監(jiān)聽這么一說,那么它怎么會被回調(diào)呢?它沒有用object的setter機(jī)制,而是臟檢查機(jī)制。臟檢查的核心,就是$digest循環(huán)。當(dāng)用戶執(zhí)行了某些操作之后,angular內(nèi)部會調(diào)用$digest(),最終導(dǎo)致界面重新渲染。那么它究竟是怎么一回事呢?

調(diào)用$watch之后,對應(yīng)的信息被綁定到angular內(nèi)部的一個$$watchers中,它是一個隊列(數(shù)組),而當(dāng)$digest被觸發(fā)時,angular就會去遍歷這個數(shù)組,并且用一個dirty變量記錄$$watchers里面記錄的那些$scope屬性是否有變化,當(dāng)有變化的時候,dirty被設(shè)置為true,在$digest執(zhí)行結(jié)束的時候,它會再檢查dirty,如果dirty為true,它會再調(diào)用自己,直到dirty為true。但是為了防止死循環(huán),angular規(guī)定,當(dāng)遞歸發(fā)生了10次或以上時,直接拋出一個錯誤,并跳出循環(huán)。

遞歸流程如下:

  1. 判斷dirty是否為true,如果為false,則不進(jìn)行$digest遞歸。(dirty默認(rèn)為true)
  2. 遍歷$$watchers,取出對應(yīng)的屬性值的老值和新值
  3. 根據(jù)objectEquality進(jìn)行新老值的對比。
  4. 如果兩個值不同,則繼續(xù)往下執(zhí)行。如果兩個值相同,則設(shè)置dirty為false。
  5. 檢查完所有的watcher之后,如果dirty還為true(這一點需要閱讀我下面的偽代碼)
  6. 設(shè)置dirty為true
  7. 用新值代替老值,這樣,在下一輪遞歸的時候,老值就是這一輪的新值
  8. 再次調(diào)用$digest

當(dāng)遞歸流程結(jié)束之后,$digest還要執(zhí)行:

將變化后的$scope重新渲染到界面

當(dāng)一個作用域創(chuàng)建完之后,$scope.$digest會被運(yùn)行一次。dirty的默認(rèn)值被設(shè)定為true,因此,如果你在controller里面使用了$watch,并且進(jìn)行了屬性賦值,往往刷新頁面就可以看到$watch的回調(diào)函數(shù)被執(zhí)行了。但是,現(xiàn)在問題來了,上面說的“angular內(nèi)部會調(diào)用$digest()”,這個內(nèi)部是怎么實現(xiàn)的?

$apply觸發(fā)$digest

在我們自己編程時,并不直接使用$digest,而是調(diào)用$scope.$apply(),$apply內(nèi)部會觸發(fā)$digest遞歸遍歷。同時,你可以給$apply傳一個參數(shù),是個函數(shù),這個函數(shù)會在$digest開始之前執(zhí)行。現(xiàn)在回到上面的問題,angular內(nèi)部怎么觸發(fā)$digest?實際上,angular里面要求你通過ng-click, ng-modal, ng-keyup等來進(jìn)行數(shù)據(jù)的雙向綁定,為什么,因為這些angular的內(nèi)部指令封裝了$apply,比如ng-click,它其實包含了document.addEventListener('click')和$scope.$apply()。

當(dāng)用戶在模板里面使用ng-click時,如下:

<div ng-click="update()">change</div>
$scope.update = function() {
 $scope.name = 'tom'
}

實際上,當(dāng)用戶點擊之后,angular內(nèi)部還會執(zhí)行$scope.$apply(),從而觸發(fā)$digest遍歷遞歸,最終觸發(fā)界面重繪。

手動調(diào)用$apply

但是有些情況下,我們不可能直接使用angular內(nèi)部指令,有兩種情況我們需要手動調(diào)用$apply,一種是調(diào)用angular內(nèi)置的語法糖,比如$http, $timeout,另一種是我們沒有使用angular內(nèi)部機(jī)制去更新了$scope,比如我們用$element.on('click', () => $scope.name = 'lucy')。也就是說“異步”和“機(jī)制外”修改$scope屬性值之后,我們都要手動調(diào)用$apply,雖然我們在調(diào)用$timeout的時候,沒有手寫$apply,但實際上它內(nèi)部確實調(diào)用了$apply:

function($timeout) {
 // 當(dāng)我們通過on('click')的方式觸發(fā)某些更新的時候,可以這樣做
 $timeout(() => {
  $scope.name = 'lily'
 })
 // 也可以這樣做
 $element.on('click', () => {
  $scope.name = 'david'
  $scope.$apply()
 })
}

但是,一定要注意,在遞歸過程中,絕對不能手動調(diào)用$apply,比如在ng-click的函數(shù)中,比如在$watch的回調(diào)函數(shù)中。

偽代碼實現(xiàn)

通過上面的講解,你可能已經(jīng)對angular里面的臟檢查已經(jīng)了解了,但是我們還是希望更深入,用代碼來把事情說清楚。我這里不去抄寫angular的源碼,而是自己寫一段偽代碼,這樣更有助于理解整個機(jī)制。

import { isEqual } from 'lodash'

class Scope {
 constructor() {
  this.$$dirty = true
  this.$$count = 0
  this.$$watchers = []
 }
 $watch(property, listener, deepEqual) {
  let watcher = {
   property,
   listener,
   deepEqual,
  }
  this.$$watchers.push(watcher)
 }
 $digest() {
  if (this.$$count >= 10) {
   throw new Error('$digest超過10次')
  }

  this.$$watchers.forEach(watcher => {
   let newValue = eval('return this.' + watcher.property)
   let oldValue = watcher.oldValue
   if (watcher.deepEqual && isEqual(newValue, oldValue)) {
    watcher.dirty = false
   } 
   else if (newValue === oldValue) {
    watcher.dirty = false
   }
   else {
    watcher.dirty = true
    eval('this.' + watcher.property + ' = ' newValue)
    watcher.listener(newValue, oldValue) // 注意,listener是在newValue賦值給$scope之后執(zhí)行的
    watcher.oldValue = newValue
   }
   // 這里的實現(xiàn)和angular邏輯里面有一點不同,angular里面,當(dāng)newValue和oldValue都為undefined時,listener會被調(diào)用,可能是angular里面在$watch的時候,會自動給$scope加上原本沒有的屬性,因此認(rèn)為是一次變動
  })
  
  this.$$count ++

  this.$$dirty = false
  for (let watcher of this.$$watchers) {
   if (watcher.dirty) {
    this.$$dirty = true
    break
   }
  }

  if (this.$$dirty) {
   this.$digest()
  }
  else {
    this.$patch()
    this.$$dirty = true
    this.$$count = 0
  }
 }
 $apply() {
  if (this.$$count) {
   return // 當(dāng)$digest執(zhí)行的過程中,不能觸發(fā)$apply
  }
  this.$$dirty = true
  this.$$count = 0
  this.$digest()
 }
 $patch() {
  // 重繪界面
 }
}
function ControllerRegister(controllerTemplate, controllerFunction) {
 let $scope = new Scope()
 $paser(controllerTemplate, $scope) // 解析controller的模板,把模板中的屬性全部都解析出來,并且把這些屬性賦值給$scope
 controllerFunction($scope) // 在controllerFunction內(nèi)部可能又給$scope添加了一些屬性,注意,不能在運(yùn)行controllerFunction的時候調(diào)用$scope.$apply()

 let properties = Object.keys($scope) // 找出$scope上的所有屬性
 // 要把$scope上的一些內(nèi)置屬性排除掉 
 properties = properties.filter(item => item.indexOf('$') !== 0) // 當(dāng)然,這種排除方法只能保證在用戶不使用$作為屬性開頭的時候有用

 properties.forEach(property => {
  $scope.$watch(property, () => {}, true)
 })

 $scope.$digest()
}

上面就是用偽代碼實現(xiàn)了angular內(nèi)部的機(jī)制,不能作為真實的引擎去使用,但是體現(xiàn)了整個臟檢查的實現(xiàn)思路。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Angular中$broadcast和$emit的使用方法詳解

    Angular中$broadcast和$emit的使用方法詳解

    本篇文章主要介紹了Angular中$broadcast和$emit的使用方法詳解,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • angularJS+requireJS實現(xiàn)controller及directive的按需加載示例

    angularJS+requireJS實現(xiàn)controller及directive的按需加載示例

    本篇文章主要介紹了angularJS+requireJS實現(xiàn)controller及directive的按需加載示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • 淺談angular表單提交中ng-submit的默認(rèn)使用方法

    淺談angular表單提交中ng-submit的默認(rèn)使用方法

    今天小編就為大家分享一篇淺談angular表單提交中ng-submit的默認(rèn)使用方法。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • Angular8升級至Angular13遇到的問題解決

    Angular8升級至Angular13遇到的問題解決

    這幾天升級公司的一個Angular項目遇到了一些問題,下面這篇文章主要給大家介紹了關(guān)于Angular8升級至Angular13遇到的問題解決,文中介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • AngularJS入門教程之AngularJS指令

    AngularJS入門教程之AngularJS指令

    AngularJS指令用于擴(kuò)展HTML。本文給大家介紹AngularJS入門教程之AngularJS指令,感興趣的朋友一起學(xué)習(xí)吧
    2016-04-04
  • Angular懶加載模塊與Combined?Injector原理全面解析

    Angular懶加載模塊與Combined?Injector原理全面解析

    這篇文章主要為大家介紹了Angular懶加載模塊與Combined?Injector原理全面解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Angular value與ngValue區(qū)別詳解

    Angular value與ngValue區(qū)別詳解

    這篇文章主要介紹了Angular value與ngValue區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • angularjs定時任務(wù)的設(shè)置與清除示例

    angularjs定時任務(wù)的設(shè)置與清除示例

    本篇文章主要介紹了angularjs定時任務(wù)的設(shè)置與清除示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Angular?Tree?Shaking優(yōu)化機(jī)制原理詳解

    Angular?Tree?Shaking優(yōu)化機(jī)制原理詳解

    這篇文章主要為大家介紹了Angular?Tree?Shaking優(yōu)化機(jī)制原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Angular2  NgModule 模塊詳解

    Angular2 NgModule 模塊詳解

    這篇文章主要介紹了Angular2 NgModule 模塊詳解的相關(guān)資料,并附簡單實例,需要的朋友可以參考下
    2016-10-10

最新評論