原文地址: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,它將打印出來10
。react
在Vue中,咱們但願total
隨着price
或quantity
更新而更新。咱們想要:編程
不幸的是,JavaScript是程序性的,而不是響應式的,因此這在現實中不起做用。爲了實現total
響應,咱們必須使用JavaScript來使事情表現得不同凡響。數組
咱們須要保存如何計算獲得total
,這樣能夠在price
或quantity
更新時從新運行它,框架
首先,須要一些方法告訴咱們的應用程序,「我即將運行的代碼,存儲它,我可能須要在其餘時間運行它。」而後咱們開始運行代碼,若是price
或quantity
變量獲得更新,再次運行存儲的代碼。 函數
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
。在繼續以前,將數據設爲對象屬性。
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
數組中的函數。
咱們須要瞭解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如何作到這一點更復雜,但你如今知道了基礎知識。
Dep
類來收集依賴項(depend
)並從新運行全部依賴項(notify
)。Watcher
程序來管理正在運行的代碼,這些代碼可能須要做爲依賴項(target
)被添加。Object.defineProperty()
建立getter
和setter
。