首頁  >  文章  >  web前端  >  深入淺析Angular 中的 zone.js,聊聊工作原理

深入淺析Angular 中的 zone.js,聊聊工作原理

青灯夜游
青灯夜游轉載
2022-02-07 10:09:302330瀏覽

這篇文章帶大家了解一下Angular 中的 zone.js,透過一個例子來展示zone.js的能力,並簡單剖析一下背後的工作原理,希望對大家有所幫助!

深入淺析Angular 中的 zone.js,聊聊工作原理

或許你聽過Angular 使用了zone.js, 但Angular 為什麼要用zone.js, 它能夠提供哪些功能呢?今天我們單獨寫一篇文章聊聊zone.js,關於它在 Angular 框架中發揮的作用將在下一篇文章中講述。 【相關教學推薦:《angular教學》】

什麼是 Zone ? 官方文件是這麼解釋的:Zone 是一個跨多個非同步任務的執行上下文。一句話總結來說,Zone 在攔截或追蹤非同步任務方面有著特別強大的能力。下面我們將透過一個範例來展示它的能力,並簡單剖析背後的工作原理。

<button id="b1">Bind Error</button>
<button id="b2">Cause Error</button>
<script>
  function main() {
    b1.addEventListener(&#39;click&#39;, bindSecondButton);
  }
  function bindSecondButton() {
    b2.addEventListener(&#39;click&#39;, throwError);
  }
  function throwError() {
    throw new Error(&#39;aw shucks&#39;);
  }
  main();
</script>

這是一個簡單的 HTML 頁面。頁面載入時會為第一個按鈕新增點擊事件,其點擊事件函數的功能是為第二個按鈕新增點擊事件,而第二個按鈕的點擊事件函數功能則是拋出一個例外。我們依序點擊第一個按鈕和第二個按鈕,控制台顯示如下:

(索引):26 Uncaught Error: aw shucks
    at HTMLButtonElement.throwError ((索引):26:13)

但是如果我們透過zone.js啟動運行程式碼,控制台輸出會有什麼不同呢,我們先調整啟動程式碼:

  Zone.current.fork(
      {
        name: &#39;error&#39;,
        onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) {
          console.log(error.stack);
        }
      }
    ).fork(Zone.longStackTraceZoneSpec).run(main);

此時控制台輸出如下:

Error: aw shucks
    at HTMLButtonElement.throwError ((索引):26:13)
    at ZoneDelegate.invokeTask (zone.js:406:31)
    at Zone.runTask (zone.js:178:47)
    at ZoneTask.invokeTask [as invoke] (zone.js:487:34)
    at invokeTask (zone.js:1600:14)
    at HTMLButtonElement.globalZoneAwareCallback (zone.js:1626:17)
    at ____________________Elapsed_571_ms__At__Mon_Jan_31_2022_20_09_09_GMT_0800_________ (localhost)
    at Object.onScheduleTask (long-stack-trace-zone.js:105:22)
    at ZoneDelegate.scheduleTask (zone.js:386:51)
    at Zone.scheduleTask (zone.js:221:43)
    at Zone.scheduleEventTask (zone.js:247:25)
    at HTMLButtonElement.addEventListener (zone.js:1907:35)
    at HTMLButtonElement.bindSecondButton ((索引):23:10)
    at ZoneDelegate.invokeTask (zone.js:406:31)
    at Zone.runTask (zone.js:178:47)
    at ____________________Elapsed_2508_ms__At__Mon_Jan_31_2022_20_09_06_GMT_0800_________ (localhost)
    at Object.onScheduleTask (long-stack-trace-zone.js:105:22)
    at ZoneDelegate.scheduleTask (zone.js:386:51)
    at Zone.scheduleTask (zone.js:221:43)
    at Zone.scheduleEventTask (zone.js:247:25)
    at HTMLButtonElement.addEventListener (zone.js:1907:35)
    at main ((索引):20:10)
    at ZoneDelegate.invoke (zone.js:372:26)
    at Zone.run (zone.js:134:43)

透過對比我們知道:不引入zone.js時,我們透過錯誤調用棧僅僅能夠知道,異常是由按鈕2的點擊函數拋出。而引入了zone.js後,我們不僅知道異常是由按鈕2的點擊函數拋出,還知道它的點擊函數是由按鈕1的點擊函數綁定的,甚至能夠知道最開始的應用啟動是main函數觸發。這種能夠持續追蹤多個非同步任務的能力在大型複雜專案中異常重要,現在我們來看看zone.js是如何做到的吧。

zone.js接手了瀏覽器提供的非同步 API,例如點擊事件、計時器等等。也正是因為這樣,它才能夠對非同步操作有更強的控制介入能力,提供更多的能力。現在我們拿點擊事件舉例,看看它是如何做到的。

proto[ADD_EVENT_LISTENER] = makeAddListener(nativeAddEventListener,..)

上述程式碼中,proto便指的是EventTarget.prototype,也就是說這行程式碼重新定義了addEventListener函數。我們繼續看看makeAddListener函數做了什麼。

function makeAddListener() {
  ......
  // 关键代码1
  nativeListener.apply(this, arguments);
  ......
  // 关键代码2
  const task = zone.scheduleEventTask(source, ...)
  ......
}

這個函數主要做了兩件事,一是在自訂函數中執行瀏覽器本身提供的addEventListener函數,另一個是為每個點擊函數安排了一個事件任務,這也是zone.js對非同步API 有強大介入能力的重要因素。

現在我們再回到本文開頭的範例中,看看控制台為什麼能夠輸出完整的完整的函數呼叫堆疊。剛剛我們分析過了makeAddListener函數,其中提到它為每個點擊函數安排了事件任務,也就是zone.scheduleEventTask函數的執行。這個安排事件任務函數最終其實執行的是onScheduleTask:

onScheduleTask: function (..., task) {
  const currentTask = Zone.currentTask;
  let trace = currentTask && currentTask.data && currentTask.data[creationTrace] || [];
  trace = [new LongStackTrace()].concat(trace);
  task.data[creationTrace] = trace;
}

文章開頭控制台輸出的完整的函數呼叫棧,儲存在currentTask.data[creationTrace]裡面,它是一個由LongStackTrace實例組成的陣列。每次有非同步任務發生時,onScheduleTask函數便把目前函式呼叫堆疊儲存下來,我們看看類別LongStackTrace的建構子就知道了:

class LongStackTrace {
    constructor() {
        this.error = getStacktrace();
        this.timestamp = new Date();
    }
}
function getStacktraceWithUncaughtError() {
    return new Error(ERROR_TAG);
}

this.error儲存的便是函數呼叫棧,getStacktrace函數通常呼叫的是getStacktraceWithUncaughtError函數,我們看到new Error大概就能夠知道整個呼叫棧是如何得來的了。

本文分析的只是zone.js能力的一個範例,如果你希望了解更多功能可以參閱官方文件。透過這個範例,希望讀者能對zone.js有一個大致的認識,因為它也是 Angular 變更偵測不可或缺的基石。這方面的內容我將在下一篇文章中講解。

更多程式相關知識,請造訪:程式設計入門! !

以上是深入淺析Angular 中的 zone.js,聊聊工作原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除