[譯]JavaScript響應式的最佳解釋
原文地址: ofollow,noindex">The Best Explanation of JavaScript Reactivity
許多前端JavaScript框架(例如Angular,React和Vue)都有自己的Reactivity引擎。通過了解響應式及其工作原理,您可以提高開發技能並更有效地使用JavaScript框架。在視訊和下面的文章中,我們構建了您在Vue原始碼中看到的相同型別的Reactivity。
:bulb:響應式系統
當你第一次看到它時,Vue的響應式系統看起來很神奇。拿這個簡單的Vue應用程式來說:


Vue
只是知道如果
price
改變,它應該做三件事:
- 更新網頁上
price
的值。 - 重新計算乘法表達式
price * quantity
,並更新頁面。 - 再次呼叫函式
totalPriceWithTax
並更新頁面。 但是等等,你是否會好奇,Vue如何知道price
更改時所要更新的內容,以及它如何跟蹤所有內容?
我們解決一個大問題是通常不會這樣程式設計。例如,如果我執行如下程式碼:

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

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

不幸的是,JavaScript是程式性的,而不是響應式的,所以這在現實中不起作用。為了實現 total
響應,我們必須使用JavaScript來使事情表現得與眾不同。
:warning: 問題
我們需要儲存如何計算得到 total
,這樣可以在 price
或 quantity
更新時重新執行它,
:white_check_mark: 解決方案
首先,需要一些方法告訴我們的應用程式,“我即將執行的程式碼, 儲存它 ,我可能需要在其他時間執行它。”然後我們開始執行程式碼,如果 price
或 quantity
變數得到更新,再次執行儲存的程式碼。

我們可以通過記錄函式來執行此操作,以便我們可以再次執行它。

target
變數中儲存了一個匿名函式,然後呼叫一個
record
函式。使用ES6箭頭語法我也可以這樣寫:

record
函式定義很簡單:

target
(在我們的例子中
{ total = price * quantity }
),所以我們可以稍後執行它,可以通過一個
replay
函式來執行儲存的所有內容。

storage
陣列中儲存的所有匿名函式。 然後在程式碼中,我們可以:

很簡單吧?如果您需要閱讀並嘗試再次理解它,這裡有完整的程式碼,僅供參考,如果您想知道原因,我會以特定的方式對此進行編碼。


:warning: 問題
我們可以根據需要繼續記錄 target
,但是有一個更強大的解決方案可以擴充套件我們的應用程式。一個負責維護 target
列表的類,當需要它們重新執行時,這些 target
列表會得到通知。
:white_check_mark: 解決方案:依賴類
我們解決這個問題的一種方法是將這種行為封裝到它自己的類中,這是一個實現標準觀察者模式的依賴類。
因此,如果我們建立一個JavaScript類來管理我們的依賴項(它更接近Vue的處理方式),它可能看起來像這樣:

subscribers
而不是
storage
。我們現在呼叫的函式是
depend
而不是
record
,我們現在使用
notify
而不是
replay
。為了讓這個執行:

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

(這只是上面的程式碼) 我們可以改為:

:white_check_mark: 解決方案:觀察者函式
在我們的 Watcher
函式中,我們可以做一些簡單的事情:

watcher
函式接受一個
myFunc
引數,將其設定為全域性
target
屬性,呼叫
dep.depend()
將
target
新增到訂閱者
subscriber
,執行
target
函式,然後重置
target
函式。
現在,當我們執行以下內容時:


您可能想知道為什麼我們將target設為全域性變數,而不是將其傳遞到我們需要的函式中。這將在我們的文章結尾處解釋。
:warning: 問題
我們有一個單獨的 Dep class
,但我們真正想要的是每個變數都有自己的 Dep
。在繼續之前,將資料設為物件屬性。

price
和
quantity
)都有自己的內部
Dep
類。

現在我們執行時:

data.price
的值,我希望
price
屬性的
Dep
類將儲存在
target
的匿名函式推送到其
subscriber
陣列(通過呼叫
dep.depend()
)。由於
data.quantity
被訪問,我還希望
quantity
屬性
Dep
類將儲存在
target
的匿名函式推送到其
subscriber
陣列中。

data.price
被訪問,我希望它只是推送到
price
屬性
Dep
類。

price
的
subscribers
什麼時候呼叫
dep.notify()
?我希望在
price
設定時呼叫它們。在文章的最後,我希望能夠進入控制檯並執行:

price
或
quantity
),所以當它被訪問時,我們可以儲存
target
到我們的
subscribers
陣列中,當它被更改時,執行儲存在
subscribers
陣列中的函式。
:white_check_mark: 解決方案:Object.defineProperty()
我們需要了解 Object.defineProperty() 函式,它是簡單的ES5 JavaScript。它允許我們為屬性定義 getter
和 setter
函式。在我向您展示如何在 Dep
類中使用它之前,將向您展示最基本的用法。


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

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

get
並
set
值時,我們可以獲得通知。通過一些遞迴,我們可以為資料陣列中的所有項執行它,對吧?
Object.keys(data)
返回物件鍵的陣列。

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

將兩種想法放在一起

price
值時,我們想要
price
記住這個匿名函式
(target)
。這樣,如果
price
被更改,或者
set
為新值,它將觸發此函式以重新執行,因為它知道此行依賴於它。
Get=>記住當前匿名函式,當我們的值發生變化時,會再次執行它。
Set=>執行儲存的匿名函式,我們的值隨之改變。
或者就我們的 Dep Class
而言
Price accessed (get)=>呼叫 dep.depend()
以儲存當前 target
Price set=>呼叫 dep.notify()
給 price
,重新執行全部 targets
讓我們結合這兩個想法,並完成我們的最終程式碼。

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

正是我們所希望的! price
和 quantity
確實都響應了!每當值 price
或 quantity
更新時,全部的程式碼都會重新執行。
Vue文件中的這個插圖現在應該開始有意義了。

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

是的,這現在不是更有意義嗎?
顯然,Vue如何做到這一點更復雜,但你現在知道了基礎知識。