試讀angular源碼第三章:初始化zone

直接看人話總結git

前言

承接上一章angularjs

該文章項目地址github

文章地址ajax

angular 版本:8.0.0-rc.4typescript

歡迎看看個人類angular框架bootstrap

文章列表

試讀angular源碼第一章:開場與platformBrowserDynamicapi

試讀angular源碼第二章:引導模塊bootstrapModule瀏覽器

試讀angular源碼第三章:初始化zone緩存

試讀angular源碼第四章:angular模塊及JIT編譯模塊app

angular觸發髒檢查的歷程

angularjs 時代,經過觸發 $scope.$apply $scope.$digest 來通知進行髒檢查並更新視圖。

從 angularjs 的行爲中,谷歌大佬們發現,全部的視圖變動都來自於下面幾種行爲:

  1. 瀏覽器事件:onclick, onmouseover, onkeyup
  2. 定時器:setInterval, setTimeout, setImmediate
  3. 異步api:ajaxfetchPromise.then
  4. 生命週期

angular 便把在 Dart 中實踐過的 zone 的技術在 JavaScript 中實現了一次。

什麼是zone

Dart 中的異步操做是沒法被當前代碼 try/cacth 的,而在 Dart 中你能夠給執行對象指定一個 zone,相似提供一個上下文執行環境

而在這個執行環境內,你就能夠所有能夠捕獲、攔截或修改一些代碼行爲,好比全部未被處理的異常。

用人話說,zone 就是一個相似 JavaScript 中的執行上下文,提供了一個環境或者過程。

每個異步方法的執行都在 zone 都被當作爲一個Task,並在Task的基礎上,zone 爲開發者提供了執行先後的鉤子函數,來得到執行先後的信息。

Zone

大概講下 Zone

zone.js/lib/zone.ts

const Zone: ZoneType = (function(global: any) {
  ...
  class Zone implements AmbientZone {
    ...
  }
  ...
  let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)};
  ...
  return global['Zone'] = Zone;
})(global);
複製代碼

Zone 是個自執行函數

執行的時候會建立一個 parentzoneSpec 都是 null,而且 name<root>Zone 實例,因此 Zone 是一顆有惟一根節點的樹

執行的末尾經過把 class Zone 賦值給頂層變量的 Zone 屬性。

ZoneDelegate代理

瞭解下 zone 的代理 ZoneDelegate

zoneSpecfork 子 zone 的時候傳入的配置對象

Zone 初始化本身的代理 ZoneDelegate 時,會把 Zone 實例 和父級的 zoneSpec 傳入

Zone 初始化時,會同步初始化一個代理 ZoneDelegate

zone.js/lib/zone.ts

class Zone implements AmbientZone {
  ...
  constructor(parent: Zone|null, zoneSpec: ZoneSpec|null) {
      // 註釋:zoneSpec 就是 fork的時候那個配置對象
      this._parent = parent;
      this._name = zoneSpec ? zoneSpec.name || 'unnamed' : '<root>';
      this._properties = zoneSpec && zoneSpec.properties || {};
      // 註釋:實現 zone 代理
      this._zoneDelegate =
          new ZoneDelegate(this, this._parent && this._parent._zoneDelegate, zoneSpec);
    }
  ...
}
複製代碼

ZoneDelegate 的構造函數把經過 fork 方法建立 Zone 時傳入的配置和鉤子函數初始化到 ZoneDelegate 代理實例上:

zone.js/lib/zone.ts

class ZoneDelegate implements AmbientZoneDelegate {
  ...
  constructor(zone: Zone, parentDelegate: ZoneDelegate|null, zoneSpec: ZoneSpec|null) {
    ...
    // 註釋:zoneSpec 就是 fork的時候那個配置對象
     this._forkZS = zoneSpec && (zoneSpec && zoneSpec.onFork ? zoneSpec : parentDelegate!._forkZS);
      this._forkDlgt = zoneSpec && (zoneSpec.onFork ? parentDelegate : parentDelegate!._forkDlgt);
      this._forkCurrZone = zoneSpec && (zoneSpec.onFork ? this.zone : parentDelegate!.zone);
    ...
  }

  fork(targetZone: Zone, zoneSpec: ZoneSpec): AmbientZone {
      // 註釋:若是有 onFork 鉤子就執行
      return this._forkZS ? this._forkZS.onFork!(this._forkDlgt!, this.zone, targetZone, zoneSpec) :
                            new Zone(targetZone, zoneSpec);
  }
  ...
}
複製代碼

最後,實際上代理執行鉤子的時候,好比 zone.fork 的時候的配置對象內有鉤子函數,那麼就會調用鉤子函數來執行

每一個 Zone 都會有一個 ZoneDelegate 代理實例,主要Zone 調用傳入的回調函數,創建、調用回調函數中的異步任務,捕捉異步任務的錯誤

Zone.__load_patch加載異步補丁

zone 經過 monkey patch 的方式,暴力將瀏覽器內的異步API進行封裝並替換掉,這一塊就在這裏。

這樣當在 Zone 的上下文內運行時,並能夠經過 Zone.current 來通知 angular 進行到了哪裏並進行變動檢測(其實也就是所謂的觸發髒檢查)(至於若是觸發的咱們放到下章再講)

這部分在打包 zonejs 的時候就將這幾個替換API操做的文件根據平臺打包到一塊兒了,舉個瀏覽器的例子:

zone.js/lib/browser/browser.ts

Zone.__load_patch('timers', (global: any) => {
  const set = 'set';
  const clear = 'clear';
  patchTimer(global, set, clear, 'Timeout');
  patchTimer(global, set, clear, 'Interval');
  patchTimer(global, set, clear, 'Immediate');
});
複製代碼

具體加載定時器補丁的方法:

zone.js/lib/common/timers.ts

export function patchTimer(window: any, setName: string, cancelName: string, nameSuffix: string) {
  let setNative: Function|null = null;
  let clearNative: Function|null = null;
  ...
  setNative =
      patchMethod(window, setName, (delegate: Function) => function(self: any, args: any[]) {
        ...
      });
    ...
}
複製代碼

經過 patchMethod 將原生API替換爲被 zone 封裝過的API來得到與 Zone 通訊並觸發鉤子函數的能力:

zone.js/lib/common/utils.ts

export function patchMethod(
    target: any, name: string,
    patchFn: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) =>
        any): Function|null {
  let proto = target;
  while (proto && !proto.hasOwnProperty(name)) {
    proto = ObjectGetPrototypeOf(proto);
  }
  if (!proto && target[name]) {
    // somehow we did not find it, but we can see it. This happens on IE for Window properties.
    proto = target;
  }

  const delegateName = zoneSymbol(name);
  let delegate: Function|null = null;
  if (proto && !(delegate = proto[delegateName])) {
    delegate = proto[delegateName] = proto[name];
    // check whether proto[name] is writable
    // some property is readonly in safari, such as HtmlCanvasElement.prototype.toBlob
    const desc = proto && ObjectGetOwnPropertyDescriptor(proto, name);
    if (isPropertyWritable(desc)) {
      const patchDelegate = patchFn(delegate!, delegateName, name);
      proto[name] = function() {
        return patchDelegate(this, arguments as any);
      };
      attachOriginToPatched(proto[name], delegate);
      if (shouldCopySymbolProperties) {
        copySymbolProperties(delegate, proto[name]);
      }
    }
  }
  return delegate;
}
複製代碼

最後用一個全局變量 patches: {[key: string]: any} 存儲打過的補丁,能夠用來判斷 zone 是否已經上過補丁等。

zone.js/lib/zone.ts

const patches: {[key: string]: any} = {};

class Zone implements AmbientZone {
  // 註釋:經過該方法緩存猴子補丁
  static __load_patch(name: string, fn: _PatchFn): void {
      if (patches.hasOwnProperty(name)) {
        if (checkDuplicate) {
          throw Error('Already loaded patch: ' + name);
        }
      } else if (!global['__Zone_disable_' + name]) {
        const perfName = 'Zone:' + name;
        mark(perfName);
        patches[name] = fn(global, Zone, _api);
        performanceMeasure(perfName, perfName);
      }
    }
}
複製代碼

Task異步任務

在 zone 中,每種異步都被稱爲任務 :Task

type TaskType = 'microTask'|'macroTask'|'eventTask';
type TaskState = 'notScheduled'|'scheduling'|'scheduled'|'running'|'canceling'|'unknown';
interface Task {
  type: TaskType;
  state: TaskState;
  source: string;
  invoke: Function;
  callback: Function;
  data?: TaskData;
  scheduleFn?: (task: Task) => void;
  cancelFn?: (task: Task) => void;
  readonly zone: Zone;
  runCount: number;
  cancelScheduleRequest(): void;
}
複製代碼

Task 分爲三種:

  1. MicroTask:在當前task結束以後和下一個task開始以前執行的,不可取消,如 Promise,MutationObserver、process.nextTick
  2. MacroTask:一段時間後才執行的task,能夠取消,如 setTimeout, setInterval, setImmediate, I/O, UI rendering
  3. EventTask:監聽事件,可能執行0次或屢次,執行時間是不肯定的

只有這三種,因此像 DOM0 級別事件如 img.onload=()=>{},在 angular裏面是沒法觸發髒檢查的。

Task 的狀態則有 'notScheduled'|'scheduling'|'scheduled'|'running'|'canceling'|'unknown';

而設置執行運行時的鉤子則須要在 zone.fork 時設置配置,看這裏:

zone.js/lib/zone.ts

interface ZoneSpec {
  ...
  /** * Allows interception of task scheduling. */
  onScheduleTask?:
      (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) => Task;

  onInvokeTask?:
      (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task,
       applyThis: any, applyArgs?: any[]) => any;

  /** * Allows interception of task cancellation. */
  onCancelTask?:
      (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) => any;

  /** * Notifies of changes to the task queue empty status. */
  onHasTask?:
      (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
       hasTaskState: HasTaskState) => void;
}
複製代碼
  1. onScheduleTask 建立異步任務
  2. onInvokeTask 執行異步任務
  3. onCancelTask 取消異步任務
  4. onHasTask 通知任務隊列空狀態的更改

經過設置這幾種鉤子,angular 就能知道某些異步任務執行的哪一步,也能夠經過鉤子去觸發髒檢查

實例化ngZone

angular 啓動 zonejs 是在上文說過的 bootstrapModule 階段:

angular/packages/core/src/application_ref.ts

@Injectable()
export class PlatformRef {
  ...
   bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):
      Promise<NgModuleRef<M>> {
    // Note: We need to create the NgZone _before_ we instantiate the module,
    // as instantiating the module creates some providers eagerly.
    // So we create a mini parent injector that just contains the new NgZone and
    // pass that as parent to the NgModuleFactory.
    const ngZoneOption = options ? options.ngZone : undefined;
    const ngZone = getNgZone(ngZoneOption);
    const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];
    // Attention: Don't use ApplicationRef.run here,
    // as we want to be sure that all possible constructor calls are inside `ngZone.run`!
    // 註釋:會被 onInvoke 執行
    return ngZone.run(() => {
      ...
    });
   }
   ...
}
複製代碼

在實例化模塊工廠以前,經過 getNgZone 獲取了一個 NgZone 實例:

angular/packages/core/src/application_ref.ts

function getNgZone(ngZoneOption?: NgZone | 'zone.js' | 'noop'): NgZone {
  let ngZone: NgZone;

  if (ngZoneOption === 'noop') {
    ngZone = new NoopNgZone();
  } else {
    ngZone = (ngZoneOption === 'zone.js' ? undefined : ngZoneOption) ||
        new NgZone({enableLongStackTrace: isDevMode()});
  }
  return ngZone;
}
複製代碼

ngZone

angular/packages/core/src/zone/ng_zone.ts

export class NgZone {
  ...

  constructor({enableLongStackTrace = false}) {
    if (typeof Zone == 'undefined') {
      throw new Error(`In this configuration Angular requires Zone.js`);
    }

    Zone.assertZonePatched();
    const self = this as any as NgZonePrivate;
    self._nesting = 0;

    self._outer = self._inner = Zone.current;

    if ((Zone as any)['wtfZoneSpec']) {
      self._inner = self._inner.fork((Zone as any)['wtfZoneSpec']);
    }

    if ((Zone as any)['TaskTrackingZoneSpec']) {
      self._inner = self._inner.fork(new ((Zone as any)['TaskTrackingZoneSpec'] as any));
    }

    if (enableLongStackTrace && (Zone as any)['longStackTraceZoneSpec']) {
      self._inner = self._inner.fork((Zone as any)['longStackTraceZoneSpec']);
    }

    forkInnerZoneWithAngularBehavior(self);
  }

  ...

  run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { return (this as any as NgZonePrivate)._inner.run(fn, applyThis, applyArgs) as T; } } 複製代碼
  1. 在實例化 ngZone 的時候,首先調用了 zone 的一個靜態方法 assertZonePatched,確認下 zone 是否已經打過補丁(是否替換過原生 API 至於爲何咱們往下面再說)

zone.js/lib/zone.ts

class Zone implements AmbientZone {
 static __symbol__: (name: string) => string = __symbol__;

 static assertZonePatched() {
   if (global['Promise'] !== patches['ZoneAwarePromise']) {
     throw new Error(
         'Zone.js has detected that ZoneAwarePromise `(window|global).Promise` ' +
         'has been overwritten.\n' +
         'Most likely cause is that a Promise polyfill has been loaded ' +
         'after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. ' +
         'If you must load one, do so before loading zone.js.)');
   }
 }
}
複製代碼
  1. 初始化 zone

_nestingZone 執行棧的層數(這個放後面說)

angular/packages/core/src/zone/ng_zone.ts

class NgZone {
    constructor({enableLongStackTrace = false}) {
    if (typeof Zone == 'undefined') {
      throw new Error(`In this configuration Angular requires Zone.js`);
    }

    // 註釋:確認是否已經上過zone補丁
    Zone.assertZonePatched();
    const self = this as any as NgZonePrivate;
    self._nesting = 0;
    // 註釋:此時是 root zone
    self._outer = self._inner = Zone.current;

    ...
  }
}
複製代碼

_outer_inner 爲當前全局的 zone Zone.current

zone.js/lib/zone.ts

interface ZoneType {
  /** * @returns {Zone} Returns the current [Zone]. The only way to change * the current zone is by invoking a run() method, which will update the current zone for the * duration of the run method callback. */
  current: Zone;
}
複製代碼

Zone.current 是 zone 上的一個靜態屬性,用來保存全局此刻正在使用的 zone,只能經過 zone.run 來 更改

  1. 調用 forkInnerZoneWithAngularBehavior 從當前的 zone(其實此時就是根<root>Zone) fork 出一份 angular zone,並設置鉤子

angular/packages/core/src/zone/ng_zone.ts

// 註釋:zone 是 `Zone.current` 此時是 root zone
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {
  zone._inner = zone._inner.fork({
    name: 'angular',
    properties: <any>{'isAngularZone': true},
    onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any,
                   applyArgs: any): any => {
      try {
        onEnter(zone);
        return delegate.invokeTask(target, task, applyThis, applyArgs);
      } finally {
        onLeave(zone);
      }
    },

    // 註釋:啓動 ngZone的 run
    onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function,
               applyThis: any, applyArgs: any[], source: string): any => {
      try {
        onEnter(zone);
        return delegate.invoke(target, callback, applyThis, applyArgs, source);
      } finally {
        onLeave(zone);
      }
    },

    onHasTask:
        (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
          delegate.hasTask(target, hasTaskState);
          if (current === target) {
            // We are only interested in hasTask events which originate from our zone
            // (A child hasTask event is not interesting to us)
            if (hasTaskState.change == 'microTask') {
              zone.hasPendingMicrotasks = hasTaskState.microTask;
              checkStable(zone);
            } else if (hasTaskState.change == 'macroTask') {
              zone.hasPendingMacrotasks = hasTaskState.macroTask;
            }
          }
        },

    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      delegate.handleError(target, error);
      zone.runOutsideAngular(() => zone.onError.emit(error));
      return false;
    }
  });
}
複製代碼

zone.fork建立子Zone

在上面,getNgZone 的時候會 new NgZone,

而在 NgZone 構造函數的結尾,forkInnerZoneWithAngularBehavior 中執行了 zone._inner.fork

angular/packages/core/src/zone/ng_zone.ts

export class NgZone {
  constructor({enableLongStackTrace = false}) {
    ...
    forkInnerZoneWithAngularBehavior(self);
  }
}
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {
  zone._inner = zone._inner.fork({
    name: 'angular',
    ...
  });
}
複製代碼

zone.fork 主要是建立一個子 Zone 實例,而 fork 方法主要調用構造函數中實例化的 ZoneDelegate 實例的 fork 方法:

zone.js/lib/zone.ts

class Zone implements AmbientZone {
  constructor(parent: Zone|null, zoneSpec: ZoneSpec|null) {
      this._parent = parent;
      this._name = zoneSpec ? zoneSpec.name || 'unnamed' : '<root>';
      this._properties = zoneSpec && zoneSpec.properties || {};
      this._zoneDelegate =
          new ZoneDelegate(this, this._parent && this._parent._zoneDelegate, zoneSpec);
  }

  // 註釋:fork 出子zone, zoneSpec 就是那一大堆配置
  public fork(zoneSpec: ZoneSpec): AmbientZone {
    if (!zoneSpec) throw new Error('ZoneSpec required!');
    return this._zoneDelegate.fork(this, zoneSpec);
  }
}
複製代碼

每一個 Zone 都會有一個 ZoneDelegate 代理實例,主要Zone 調用傳入的回調函數,創建、調用回調函數中的異步任務,捕捉異步任務的錯誤

這裏經過調用 ZoneDelegate 實例的 fork 方法從根 Zone 建立了一個 Zone

zone.js/lib/zone.ts

class ZoneDelegate implements AmbientZoneDelegate {
  ...
  fork(targetZone: Zone, zoneSpec: ZoneSpec): AmbientZone {
      // 註釋:若是有 onFork 鉤子就執行
      return this._forkZS ? this._forkZS.onFork!(this._forkDlgt!, this.zone, targetZone, zoneSpec) :
                            new Zone(targetZone, zoneSpec);
  }
  ...
}
複製代碼

因此,當初始化 ngZone 的時候,這個 zone._inner 就是 Zone.current,也就是 let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)}; 時候建立的 new Zone(null, null) root zone。

所以此時的 zone._inner 就是 Zone.current 其實也是 <root> Zone

因此angular zone 是從 <root>Zone fork 出的子 zone

ngZone.run執行angular的Zone

當初始化好 ZoneZoneDelegate ,angular 調用了 ngZone.run

angular/packages/core/src/zone/ng_zone.ts

export class NgZone {
  run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { return (this as any as NgZonePrivate)._inner.run(fn, applyThis, applyArgs) as T; } } 複製代碼

ngZone.run 又調用了 zone.run

zone.js/lib/zone.ts

interface _ZoneFrame {
  parent: _ZoneFrame|null;
  zone: Zone;
}

let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)};

class Zone implements AmbientZone {
   ...
   // 註釋:經過this._zoneDelegate.invoke執行一個函數
   public run(callback: Function, applyThis?: any, applyArgs?: any[], source?: string): any;
   public run<T>(callback: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], source?: string): T { _currentZoneFrame = {parent: _currentZoneFrame, zone: this}; try { return this._zoneDelegate.invoke(this, callback, applyThis, applyArgs, source); } finally { _currentZoneFrame = _currentZoneFrame.parent!; } } ... } 複製代碼

_currentZoneFrame 是一個全局對象,保存了當前系統中的 zone 幀鏈

在初始化的時候,會建立一個 parent: null, zone: new Zone 的根 _currentZoneFrame,所以Zone 就是在這裏被建立的

它有兩個屬性:

  1. parent 指向了父 zoneFrame
  2. zone 指向了當前激活的zone對象

因此 _currentZoneFrame 並非固定不變的。

ngZone.run 又觸發了 this._zoneDelegate.invoke

zoneDelegate.invoke執行方法

zone 是經過 this._zoneDelegate.invoke 執行一個方法的:

angular/packages/core/src/zone/ng_zone.ts

class ZoneDelegate implements AmbientZoneDelegate {
  private _invokeZS: ZoneSpec|null;
  ...
  constructor(zone: Zone, parentDelegate: ZoneDelegate|null, zoneSpec: ZoneSpec|null) {
    this._invokeZS = zoneSpec && (zoneSpec.onInvoke ? zoneSpec : parentDelegate!._invokeZS);
  }
  ...
  invoke(
    targetZone: Zone, callback: Function, applyThis: any, applyArgs?: any[], source?: string): any {
    return this._invokeZS ? this._invokeZS.onInvoke!
                           (this._invokeDlgt!, this._invokeCurrZone!, targetZone, callback,
                            applyThis, applyArgs, source) :
                           callback.apply(applyThis, applyArgs);
  }
  ...
}
複製代碼

invoke 方法接受4個參數:

  1. targetZone: Zone 當前調用 ZoneDelegateZone 實例
  2. callback: Function 回調函數,其實就是 zone.run(callback) 傳入的那個函數,實例化模塊組件的函數
  3. applyThis: any 須要綁定的 this
  4. applyArgs?: any[] 回調函數的參數
  5. source?: string 資源暫時不知道幹嗎的

invoke 方法的做用就是:若是 this._invokeZS 存在而且有 onInvoke 鉤子就用 this._invokeZS.onInvoke 執行回調,不然僅僅調用回調函數

因此回到一開始實例化 ngZone 的最後 forkInnerZoneWithAngularBehavior 的代碼:

angular/packages/core/src/zone/ng_zone.ts

function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {
  zone._inner = zone._inner.fork({
    name: 'angular',
    ....
    onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function,
               applyThis: any, applyArgs: any[], source: string): any => {
      try {
        onEnter(zone);
        return delegate.invoke(target, callback, applyThis, applyArgs, source);
      } finally {
        onLeave(zone);
      }
    },

    ...
  });
}
複製代碼

因此當 onInvoke 鉤子調用了 run 的回調的時候,會前後觸發 onEnter(zone); onLeave(zone);

angular/packages/core/src/zone/ng_zone.ts

function onEnter(zone: NgZonePrivate) {
  zone._nesting++;
  if (zone.isStable) {
    zone.isStable = false;
    zone.onUnstable.emit(null);
  }
}

function onLeave(zone: NgZonePrivate) {
  zone._nesting--;
  checkStable(zone);
}
複製代碼

當進入執行棧時,ngZone._nesting ++ 離開 --

鉤子函數 onInvoke 又調用了 delegate: ZoneDelegateangular zone 的父級 <Zone>zoneinvoke 方法:

zone.js/lib/zone.ts

class ZoneDelegate implements AmbientZoneDelegate {
  ...
  invoke(
        targetZone: Zone, callback: Function, applyThis: any, applyArgs?: any[],
        source?: string): any {
      return this._invokeZS ? this._invokeZS.onInvoke!
                              (this._invokeDlgt!, this._invokeCurrZone!, targetZone, callback,
                               applyThis, applyArgs, source) :
                              callback.apply(applyThis, applyArgs);
    }
    ...
}
複製代碼

可是由於 <root>Zone 沒有 ZoneDelegate ,因此只是執行了 callback.apply(applyThis, applyArgs);

那麼爲何這麼作呢,onInvoke 遞歸調用 delegate.invoke

由於 Zone 實例實際上是個樹形結構

我猜想 angular 想讓執行時通過層層傳遞觸發每個父級 Zone 的代理對象並觸發相應的鉤子函數調起對應的操做,最後交由根代理對象來執行真正的回調函數

因此稍微總結下:

  1. ngZone.run 其實就是調用了建立的 angular zone 的 run 方法
  2. zone.run 又調用了 fork angular 的時候傳入的配置 onInvoke 鉤子
  3. 配置 onInvoke 鉤子又執行了傳入 run 的回調即:onInvoke 鉤子執行了建立模塊和組件的函數
  4. 相似 AOP ,在 onInvoke 執行回調時切入切面,會經過 onEnter(zone); onLeave(zone); 來用 EventEmitter 通知 angular

到此爲止,初始化好了 zone 的運行環境

總結

用人話總結下:

  1. 在 zone.js 被引入時,自執行函數 Zone 建立 <root>Zone
  2. 在 zone.js 被引入時,執行替換原生異步API的補丁
  3. bootstrapModuleFactory 在引導根模塊時,先會用 getNgZone<root>Zone 出一個 angular Zone,並設置幾個鉤子
  4. 調用 ngZone.run 並傳入實例化模塊工廠和組件的回調函數
  5. ngZone.run 調用 angular Zonerun 方法
  6. angular Zonerun 方法調用 forkangular Zone 時傳入的配置中的 onInvoke 執行
  7. angular ZoneonInvoke 觸發進入/離開切面的操做,並調起父級 zone 的代理的 onInvoke 鉤子函數
  8. 由子 Zone 到祖 Zone 遞歸執行 onInvoke 鉤子,觸發對應的切面函數
  9. 最後<root>Zone 執行實例化模塊工廠和組件的回調函數
相關文章
相關標籤/搜索