[譯]JavaScript響應式的最佳解釋

sunshine楊小咩發表於2018-09-27

原文地址:The Best Explanation of JavaScript Reactivity

許多前端JavaScript框架(例如Angular,React和Vue)都有自己的Reactivity引擎。通過了解響應式及其工作原理,您可以提高開發技能並更有效地使用JavaScript框架。在視訊和下面的文章中,我們構建了您在Vue原始碼中看到的相同型別的Reactivity。

?響應式系統

當你第一次看到它時,Vue的響應式系統看起來很神奇。拿這個簡單的Vue應用程式來說:

[譯]JavaScript響應式的最佳解釋
[譯]JavaScript響應式的最佳解釋
不知何故Vue只是知道如果price改變,它應該做三件事:

  • 更新網頁上price的值。
  • 重新計算乘法表示式price * quantity,並更新頁面。
  • 再次呼叫函式totalPriceWithTax並更新頁面。 但是等等,你是否會好奇,Vue如何知道price更改時所要更新的內容,以及它如何跟蹤所有內容?
    [譯]JavaScript響應式的最佳解釋
    這並不是JavaScript通常的工作方式

我們解決一個大問題是通常不會這樣程式設計。例如,如果我執行如下程式碼:

[譯]JavaScript響應式的最佳解釋

你覺得它列印什麼?由於我們沒有使用Vue,它將列印出來10

[譯]JavaScript響應式的最佳解釋

在Vue中,我們希望total隨著pricequantity更新而更新。我們想要:

[譯]JavaScript響應式的最佳解釋

不幸的是,JavaScript是程式性的,而不是響應式的,所以這在現實中不起作用。為了實現total響應,我們必須使用JavaScript來使事情表現得與眾不同。

⚠️ 問題

我們需要儲存如何計算得到total,這樣可以在pricequantity更新時重新執行它,

✅ 解決方案

首先,需要一些方法告訴我們的應用程式,“我即將執行的程式碼,儲存它,我可能需要在其他時間執行它。”然後我們開始執行程式碼,如果pricequantity變數得到更新,再次執行儲存的程式碼。

[譯]JavaScript響應式的最佳解釋
我們可以通過記錄函式來執行此操作,以便我們可以再次執行它。
[譯]JavaScript響應式的最佳解釋
請注意,我們在target變數中儲存了一個匿名函式,然後呼叫一個record函式。使用ES6箭頭語法我也可以這樣寫:
[譯]JavaScript響應式的最佳解釋
這個record函式定義很簡單:
[譯]JavaScript響應式的最佳解釋
我們正在儲存target(在我們的例子中{ total = price * quantity }),所以我們可以稍後執行它,可以通過一個replay函式來執行儲存的所有內容。
[譯]JavaScript響應式的最佳解釋
這將遍歷執行storage 陣列中儲存的所有匿名函式。 然後在程式碼中,我們可以:
[譯]JavaScript響應式的最佳解釋
很簡單吧?如果您需要閱讀並嘗試再次理解它,這裡有完整的程式碼,僅供參考,如果您想知道原因,我會以特定的方式對此進行編碼。
[譯]JavaScript響應式的最佳解釋

[譯]JavaScript響應式的最佳解釋

⚠️ 問題

我們可以根據需要繼續記錄target,但是有一個更強大的解決方案可以擴充套件我們的應用程式。一個負責維護target列表的類,當需要它們重新執行時,這些target列表會得到通知。

✅ 解決方案:依賴類

我們解決這個問題的一種方法是將這種行為封裝到它自己的類中,這是一個實現標準觀察者模式的依賴類。

因此,如果我們建立一個JavaScript類來管理我們的依賴項(它更接近Vue的處理方式),它可能看起來像這樣:

[譯]JavaScript響應式的最佳解釋
請注意,我們現在儲存匿名函式是subscribers而不是storage。我們現在呼叫的函式是depend而不是record,我們現在使用notify而不是replay。為了讓這個執行:
[譯]JavaScript響應式的最佳解釋
它仍然有效,現在的程式碼感覺更可重用。唯一仍然感覺有點奇怪的是設定和執行target

⚠️ 問題

我們將為每個變數設定一個Dep類,並且很好地封裝了建立需要監視更新的匿名函式的行為。也許一個watcher函式可能是為了處理這種行為。

所以不要這樣呼叫:

[譯]JavaScript響應式的最佳解釋
(這只是上面的程式碼) 我們可以改為:
[譯]JavaScript響應式的最佳解釋

✅ 解決方案:觀察者函式

在我們的Watcher函式中,我們可以做一些簡單的事情:

[譯]JavaScript響應式的最佳解釋
如您所見,該watcher函式接受一個myFunc引數,將其設定為全域性target屬性,呼叫dep.depend()target新增到訂閱者subscriber,執行target函式,然後重置target函式。

現在,當我們執行以下內容時:

[譯]JavaScript響應式的最佳解釋
[譯]JavaScript響應式的最佳解釋
您可能想知道為什麼我們將target設為全域性變數,而不是將其傳遞到我們需要的函式中。這將在我們的文章結尾處解釋。

⚠️ 問題

我們有一個單獨的Dep class,但我們真正想要的是每個變數都有自己的Dep。在繼續之前,將資料設為物件屬性。

[譯]JavaScript響應式的最佳解釋
假設每個屬性(pricequantity)都有自己的內部Dep類。
[譯]JavaScript響應式的最佳解釋
現在我們執行時:
[譯]JavaScript響應式的最佳解釋
由於訪問了data.price的值,我希望price屬性的Dep類將儲存在target的匿名函式推送到其subscriber 陣列(通過呼叫dep.depend())。由於data.quantity被訪問,我還希望quantity屬性Dep類將儲存在target的匿名函式推送到其subscriber 陣列中。
[譯]JavaScript響應式的最佳解釋
如果我有另一個匿名函式,只是data.price被訪問,我希望它只是推送到price屬性Dep類。
[譯]JavaScript響應式的最佳解釋
pricesubscribers什麼時候呼叫dep.notify()?我希望在price設定時呼叫它們。在文章的最後,我希望能夠進入控制檯並執行:
[譯]JavaScript響應式的最佳解釋
我們需要一些方式來掛鉤資料屬性(如pricequantity),所以當它被訪問時,我們可以儲存target到我們的 subscribers陣列中,當它被更改時,執行儲存在 subscribers陣列中的函式。

✅ 解決方案:Object.defineProperty()

我們需要了解Object.defineProperty()函式,它是簡單的ES5 JavaScript。它允許我們為屬性定義gettersetter函式。在我向您展示如何在Dep類中使用它之前,將向您展示最基本的用法。

[譯]JavaScript響應式的最佳解釋

[譯]JavaScript響應式的最佳解釋

如您所見,它只記錄兩行。但是,它實際上沒有getset任何值,因為我們過度使用了該功能。我們現在加回來吧。get()期望返回一個值,set()仍然需要更新一個值,所以讓我們新增一個internalValue變數來儲存我們當前的price值。

[譯]JavaScript響應式的最佳解釋

既然我們的getset工作正常,您認為將列印到控制檯的是什麼?

[譯]JavaScript響應式的最佳解釋
因此,當我們getset值時,我們可以獲得通知。通過一些遞迴,我們可以為資料陣列中的所有項執行它,對吧?

Object.keys(data)返回物件鍵的陣列。

[譯]JavaScript響應式的最佳解釋

現在一切都有gettersetter,我們在控制檯上看到了這一點。

[譯]JavaScript響應式的最佳解釋

? 將兩種想法放在一起

[譯]JavaScript響應式的最佳解釋
當像這樣的一段程式碼執行並getprice值時,我們想要price記住這個匿名函式(target)。這樣,如果price被更改,或者set為新值,它將觸發此函式以重新執行,因為它知道此行依賴於它。

Get =>記住當前匿名函式,當我們的值發生變化時,會再次執行它。

Set =>執行儲存的匿名函式,我們的值隨之改變。

或者就我們的Dep Class而言

Price accessed (get) =>呼叫dep.depend()以儲存當前target

Price set =>呼叫dep.notify()price,重新執行全部targets

讓我們結合這兩個想法,並完成我們的最終程式碼。

[譯]JavaScript響應式的最佳解釋

現在看看我們執行時會發生什麼。

[譯]JavaScript響應式的最佳解釋

正是我們所希望的!pricequantity確實都響應了!每當值pricequantity更新時,全部的程式碼都會重新執行。

Vue文件中的這個插圖現在應該開始有意義了。

[譯]JavaScript響應式的最佳解釋

你看到那個美麗的紫色資料圈getters and setters了嗎?看起來應該很熟悉!每個元件例項都有一個watcher例項(藍色),它從getter(紅線)收集依賴項。稍後呼叫setter時,它會通知watcher導致元件重新渲染。註釋之後的圖如下。

[譯]JavaScript響應式的最佳解釋

是的,這現在不是更有意義嗎?

顯然,Vue如何做到這一點更復雜,但你現在知道了基礎知識。

⏪ 那麼我們學到了什麼?

  • 如何建立一個Dep類來收集依賴項(depend)並重新執行所有依賴項(notify)。
  • 如何建立一個Watcher程式來管理正在執行的程式碼,這些程式碼可能需要作為依賴項(target)被新增。
  • 如何使用Object.defineProperty()建立gettersetter

擴充套件

可以看看reactivity and proxies

相關文章