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

原文地址:The Best Explanation of JavaScript Reactivityjavascript

許多前端JavaScript框架(例如Angular,React和Vue)都有本身的Reactivity引擎。經過了解響應式及其工做原理,您能夠提升開發技能並更有效地使用JavaScript框架。在視頻和下面的文章中,咱們構建了您在Vue源代碼中看到的相同類型的Reactivity。前端

💡響應式系統

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

不知何故 Vue只是知道若是 price改變,它應該作三件事:

  • 更新網頁上price的值。
  • 從新計算乘法表達式price * quantity,並更新頁面。
  • 再次調用函數totalPriceWithTax並更新頁面。 可是等等,你是否會好奇,Vue如何知道price更改時所要更新的內容,以及它如何跟蹤全部內容?
    這並非JavaScript一般的工做方式

咱們解決一個大問題是一般不會這樣編程。例如,若是我運行以下代碼: java

你以爲它打印什麼?因爲咱們沒有使用Vue,它將打印出來10react

在Vue中,咱們但願total隨着pricequantity更新而更新。咱們想要:編程

不幸的是,JavaScript是程序性的,而不是響應式的,因此這在現實中不起做用。爲了實現total響應,咱們必須使用JavaScript來使事情表現得不同凡響。數組

⚠️ 問題

咱們須要保存如何計算獲得total,這樣能夠在pricequantity更新時從新運行它,框架

✅ 解決方案

首先,須要一些方法告訴咱們的應用程序,「我即將運行的代碼,存儲它,我可能須要在其餘時間運行它。」而後咱們開始運行代碼,若是pricequantity變量獲得更新,再次運行存儲的代碼。 函數

咱們能夠經過記錄函數來執行此操做,以便咱們能夠再次運行它。
請注意,咱們在 target變量中存儲了一個匿名函數,而後調用一個 record函數。使用ES6箭頭語法我也能夠這樣寫:
這個 record函數定義很簡單:
咱們正在存儲 target(在咱們的例子中 { total = price * quantity }),因此咱們能夠稍後運行它,能夠經過一個 replay函數來運行存儲的全部內容。
這將遍歷執行 storage 數組中存儲的全部匿名函數。 而後在代碼中,咱們能夠:
很簡單吧?若是您須要閱讀並嘗試再次理解它,這裏有完整的代碼,僅供參考,若是您想知道緣由,我會以特定的方式對此進行編碼。

⚠️ 問題

咱們能夠根據須要繼續記錄target,可是有一個更強大的解決方案能夠擴展咱們的應用程序。一個負責維護target列表的類,當須要它們從新運行時,這些target列表會獲得通知。編碼

✅ 解決方案:依賴類

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

所以,若是咱們建立一個JavaScript類來管理咱們的依賴項(它更接近Vue的處理方式),它可能看起來像這樣:

請注意,咱們如今存儲匿名函數是 subscribers而不是 storage。咱們如今調用的函數是 depend而不是 record,咱們如今使用 notify而不是 replay。爲了讓這個運行:
它仍然有效,如今的代碼感受更可重用。惟一仍然感受有點奇怪的是設置和運行 target

⚠️ 問題

咱們將爲每一個變量設置一個Dep類,而且很好地封裝了建立須要監視更新的匿名函數的行爲。也許一個watcher函數多是爲了處理這種行爲。

因此不要這樣調用:

(這只是上面的代碼) 咱們能夠改成:

✅ 解決方案:觀察者函數

在咱們的Watcher函數中,咱們能夠作一些簡單的事情:

如您所見,該 watcher函數接受一個 myFunc參數,將其設置爲全局 target屬性,調用 dep.depend()target添加到訂閱者 subscriber,執行 target函數,而後重置 target函數。

如今,當咱們運行如下內容時:

您可能想知道爲何咱們將target設爲全局變量,而不是將其傳遞到咱們須要的函數中。這將在咱們的文章結尾處解釋。

⚠️ 問題

咱們有一個單獨的Dep class,但咱們真正想要的是每一個變量都有本身的Dep。在繼續以前,將數據設爲對象屬性。

假設每一個屬性( pricequantity)都有本身的內部 Dep類。
如今咱們執行時:
因爲訪問了 data.price的值,我但願 price屬性的 Dep類將存儲在 target的匿名函數推送到其 subscriber 數組(經過調用 dep.depend())。因爲 data.quantity被訪問,我還但願 quantity屬性 Dep類將存儲在 target的匿名函數推送到其 subscriber 數組中。
若是我有另外一個匿名函數,只是 data.price被訪問,我但願它只是推送到 price屬性 Dep類。
pricesubscribers何時調用 dep.notify()?我但願在 price設置時調用它們。在文章的最後,我但願可以進入控制檯並執行:
咱們須要一些方式來掛鉤數據屬性(如 pricequantity),因此當它被訪問時,咱們能夠保存 target到咱們的 subscribers數組中,當它被更改時,運行存儲在 subscribers數組中的函數。

✅ 解決方案:Object.defineProperty()

咱們須要瞭解Object.defineProperty()函數,它是簡單的ES5 JavaScript。它容許咱們爲屬性定義gettersetter函數。在我向您展現如何在Dep類中使用它以前,將向您展現最基本的用法。

如您所見,它只記錄兩行。可是,它實際上沒有getset任何值,由於咱們過分使用了該功能。咱們如今加回來吧。get()指望返回一個值,set()仍然須要更新一個值,因此讓咱們添加一個internalValue變量來存儲咱們當前的price值。

既然咱們的getset工做正常,您認爲將打印到控制檯的是什麼?

所以,當咱們 getset值時,咱們能夠得到通知。經過一些遞歸,咱們能夠爲數據數組中的全部項運行它,對吧?

Object.keys(data)返回對象鍵的數組。

如今一切都有gettersetter,咱們在控制檯上看到了這一點。

🛠 將兩種想法放在一塊兒

當像這樣的一段代碼運行並 get price值時,咱們想要 price記住這個匿名函數 (target)。這樣,若是 price被更改,或者 set爲新值,它將觸發此函數以從新運行,由於它知道此行依賴於它。

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

Set =>運行保存的匿名函數,咱們的值隨之改變。

或者就咱們的Dep Class而言

Price accessed (get) =>調用dep.depend()以保存當前target

Price set =>調用dep.notify()price,從新運行所有targets

讓咱們結合這兩個想法,並完成咱們的最終代碼。

如今看看咱們執行時會發生什麼。

正是咱們所但願的!pricequantity確實都響應了!每當值pricequantity更新時,所有的代碼都會從新運行。

Vue文檔中的這個插圖如今應該開始有意義了。

你看到那個美麗的紫色數據圈getters and setters了嗎?看起來應該很熟悉!每一個組件實例都有一個watcher實例(藍色),它從getter(紅線)收集依賴項。稍後調用setter時,它會通知watcher致使組件從新渲染。註釋以後的圖以下。

是的,這如今不是更有意義嗎?

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

⏪ 那麼咱們學到了什麼?

  • 如何建立一個Dep類來收集依賴項(depend)並從新運行全部依賴項(notify)。
  • 如何建立一個Watcher程序來管理正在運行的代碼,這些代碼可能須要做爲依賴項(target)被添加。
  • 如何使用Object.defineProperty()建立gettersetter

擴展

能夠看看reactivity and proxies

相關文章
相關標籤/搜索