溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Angular變更檢測(cè)中的DOM更新機(jī)制是什么

發(fā)布時(shí)間:2022-12-13 09:36:36 來(lái)源:億速云 閱讀:86 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要介紹“Angular變更檢測(cè)中的DOM更新機(jī)制是什么”,在日常操作中,相信很多人在Angular變更檢測(cè)中的DOM更新機(jī)制是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Angular變更檢測(cè)中的DOM更新機(jī)制是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

變更檢測(cè)是Angular中很重要的一部分,也就是模型和視圖之間保持同步。在日常開(kāi)發(fā)過(guò)程中,我們無(wú)需了解變更檢測(cè),因?yàn)锳ngular都幫我們完成了這一部分工作,讓開(kāi)發(fā)人員更加專注于業(yè)務(wù)實(shí)現(xiàn),提高開(kāi)發(fā)效率和開(kāi)發(fā)體驗(yàn)。但是如果想要深入使用框架,或者想要寫(xiě)出高性能的代碼而不僅僅只是實(shí)現(xiàn)了功能,就必須要去了解變更檢測(cè),它可以幫助我們更好的理解框架,調(diào)試錯(cuò)誤,提高性能等。

Angular的DOM更新機(jī)制

我們先來(lái)看一個(gè)小例子。

當(dāng)我們點(diǎn)擊按鈕的時(shí)候,改變了name屬性,同時(shí)DOM自動(dòng)被更新成新的name值。

那現(xiàn)在有一個(gè)問(wèn)題,如果我改變name的值后,緊接著把DOM中的innerText輸出出來(lái),它會(huì)是什么值呢?

import { Component, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Empty';

  @ViewChild('textContainer') textContainer: ElementRef;

  normalClick(): void {
    this.name = 'Hello Angular';

    console.log(this.textContainer.nativeElement.innerText);
  }
}

你答對(duì)了嗎?

那這兩段代碼中到底發(fā)生了什么呢?

如果我們用原生JS來(lái)編寫(xiě)這段代碼,那么點(diǎn)擊按鈕后的視圖肯定不會(huì)發(fā)生任何變化,而在Angular中卻讓視圖發(fā)生了變化,那它為什么會(huì)自動(dòng)把視圖更新了呢?這離不開(kāi)一個(gè)叫做zone.js的庫(kù),簡(jiǎn)單來(lái)說(shuō),它是對(duì)發(fā)生值改變的事件做了一些處理,這個(gè)會(huì)在后面的部分詳細(xì)講解,這里暫時(shí)知道這個(gè)就可以了。

如果我不想讓這個(gè)庫(kù)做這些處理,Angular還為我們提供了禁用zone.js的方法。

可以在main.ts中設(shè)置禁用zone.js。

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule, {
  ngZone: 'noop'
})
  .catch(err => console.error(err));

當(dāng)我們禁用zone.js,視圖并未發(fā)生更新。到源碼里找一下視圖更新的相關(guān)代碼。

 */
class ApplicationRef {
    /** @internal */
    constructor(_zone, _injector, _exceptionHandler, _initStatus) {
        this._zone = _zone;
        this._injector = _injector;
        this._exceptionHandler = _exceptionHandler;
        this._initStatus = _initStatus;
        /** @internal */
        this._bootstrapListeners = [];
        this._views = [];
        this._runningTick = false;
        this._stable = true;
        this._destroyed = false;
        this._destroyListeners = [];
        /**
         * Get a list of component types registered to this application.
         * This list is populated even before the component is created.
         */
        this.componentTypes = [];
        /**
         * Get a list of components registered to this application.
         */
        this.components = [];
        this._onMicrotaskEmptySubscription = this._zone.onMicrotaskEmpty.subscribe({
            next: () => {
                this._zone.run(() => {
                    this.tick();
                });
            }
        });
        ...
    }

/**
     * Invoke this method to explicitly process change detection and its side-effects.
     *
     * In development mode, `tick()` also performs a second change detection cycle to ensure that no
     * further changes are detected. If additional changes are picked up during this second cycle,
     * bindings in the app have side-effects that cannot be resolved in a single change detection
     * pass.
     * In this case, Angular throws an error, since an Angular application can only have one change
     * detection pass during which all change detection must complete.
     */
    tick() {
        NG_DEV_MODE && this.warnIfDestroyed();
        if (this._runningTick) {
            const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
                'ApplicationRef.tick is called recursively' :
                '';
            throw new RuntimeError(101 /* RuntimeErrorCode.RECURSIVE_APPLICATION_REF_TICK */, errorMessage);
        }
        try {
            this._runningTick = true;
            for (let view of this._views) {
                view.detectChanges();
            }
            if (typeof ngDevMode === 'undefined' || ngDevMode) {
                for (let view of this._views) {
                    view.checkNoChanges();
                }
            }
        }
        catch (e) {
            // Attention: Don't rethrow as it could cancel subscriptions to Observables!
            this._zone.runOutsideAngular(() => this._exceptionHandler.handleError(e));
        }
        finally {
            this._runningTick = false;
        }
    }

}

大致解讀一下,這個(gè)ApplicationRef是Angular整個(gè)應(yīng)用的實(shí)例,在構(gòu)造函數(shù)中,zone(zone庫(kù))的onMicrotaskEmpty(從名字上看是一個(gè)清空微任務(wù)的一個(gè)subject)訂閱了一下。在訂閱里,調(diào)用了tick(),那tick里做了什么呢?

思考: 上次說(shuō)了最好訂閱不要放到constructor里去訂閱,這里怎么這么不規(guī)范呢?

當(dāng)然不是,上次我們說(shuō)的是Angular組件里哪些應(yīng)該放constructor,哪些應(yīng)該放ngOnInit里的情況。但這里,ApplicationRef人家是一個(gè)service呀,只能將初始化的代碼放constructor。

在tick函數(shù)里,如果發(fā)現(xiàn)這個(gè)tick函數(shù)正在執(zhí)行,則會(huì)拋出異常,因?yàn)檫@個(gè)是整個(gè)應(yīng)用的實(shí)例,不能遞歸調(diào)用。然后,遍歷了所有個(gè)views,然后每個(gè)view都執(zhí)行了detectChanges(),也就是執(zhí)行了下變更檢測(cè),什么是變更檢測(cè),會(huì)在后面詳細(xì)講解。緊接著,如果是devMode,再次遍歷所有的views,每個(gè)view執(zhí)行了checkNoChanges(),檢查一下有沒(méi)有變化,有變化則會(huì)拋錯(cuò)(后面會(huì)詳細(xì)說(shuō)這個(gè)問(wèn)題,暫時(shí)跳過(guò))。

那好了,現(xiàn)在也知道怎么能讓它更新了,就是要調(diào)用一下ApplicationReftick方法。

import { Component, ViewChild, ElementRef, ApplicationRef } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent  {
  name = 'Empty';

  @ViewChild('textContainer') textContainer: ElementRef = {} as any;

  constructor(private app: ApplicationRef){}

  normalClick(): void {
    this.name = 'Hello Angular';

    console.log(this.textContainer.nativeElement.innerText);

    this.app.tick();
  }
}

果然,可以正常的更新視圖了。

到此,關(guān)于“Angular變更檢測(cè)中的DOM更新機(jī)制是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI