Vue.js是如何作到數據響應的?

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

若是您觀看此視頻而不是閱讀文章,請觀看系列中的下一個視頻,與Vue的建立者Evan You討論反應性和代理。vue

💡 The Reactivity System

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

不知何故,Vue只知道若是價格發生變化,它應該作三件事:數組

  • 更新咱們網頁上的價格值。
  • 從新計算乘以price * quantity的表達式,並更新頁面。
  • 再次調用totalPriceWithTax函數並更新頁面。

可是等等,你應該會以爲奇怪,當價格變化時,Vue如何知道要更新什麼,以及它如何跟蹤全部內容?框架

這不是JavaScript編程常規的工做方式。ide

若是你不明白,那咱們試着看看常規的JavaScript是怎麼運行的。例如,若是我運行此代碼:函數

你以爲它打印什麼?因爲咱們沒有使用Vue,它將打印10。編碼

在Vue,咱們但願每當價格或數量更新時,總計都會獲得更新。咱們想要:idea

不幸的是,JavaScript是程序性的,而不是被動的,因此這在現實生活中不起做用。爲了使數據變化獲得相應,咱們必須使用JavaScript來使事情表現不一樣。spa

⚠️ 問題

咱們須要保存計算總數的方式,以便在價格或數量變化時從新運行。

✅ 解決方案

首先,咱們須要一些方法告訴咱們的應用程序,「我即將運行的代碼,存儲它,我可能須要你在另外一個時間運行它。」而後咱們將要運行代碼,若是價格或數量變量獲得更新,再次運行存儲的代碼。

請注意,咱們在目標變量中存儲了一個匿名函數,而後調用了一個記錄函數。使用ES6箭頭語法我也能夠這樣寫:

請注意,咱們在目標變量中存儲了一個匿名函數,而後調用了一個記錄函數。使用ES6箭頭語法我也能夠這樣寫:

記錄的方法:

咱們正在存儲目標(在咱們的例子中是{total = price * quantity}),因此咱們能夠稍後運行它。

這將遍歷存儲陣列中存儲的全部匿名函數並執行它們中的每個。

而後在咱們的代碼中,咱們能夠:

很簡單吧?若是您須要閱讀並嘗試再次掌握它,這裏的代碼就完整了。僅供參考,若是您想知道緣由,我會以特定的方式對此進行編碼。

⚠️ 問題

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

✅ 解決方法: 使用Class

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

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

讓它運行:

它仍然有效,如今咱們的代碼感受更可靠了。只有仍然感受有點奇怪的是target()的設置和運行。

⚠️ 問題

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

(這只是上面的代碼)

咱們能夠改成:

✅ 解決方案:觀察者功能

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

如您所見,watcher函數接受myFunc參數,將其設置爲咱們的全局目標屬性,調用dep.depend()以將目標添加爲訂閱者,調用目標函數並重置目標。

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

您可能想知道爲何咱們將target實現爲全局變量,而不是將其傳遞到咱們須要的函數中。
這有一個很好的理由,這將在咱們的文章結尾處揭曉。

⚠️ 問題

咱們有一個Dep類,但咱們真正想要的是每一個變量都有本身的Dep。在咱們繼續以前,先存儲一下數據。

讓咱們假設咱們的每一個屬性(價格和數量)都有本身的內部Dep類。

當咱們運行時:

因爲訪問了data.price值,我但願price屬性的Dep類將咱們的匿名函數(存儲在目標中)推送到其訂閱者數組(經過調用dep.depend())。因爲訪問了data.quantity,我還但願quantity屬性Dep類將此匿名函數(存儲在目標中)推送到其訂閱者數組中。

若是我有另外一個匿名函數,只訪問data.price,我但願只推送到價格屬性Dep類。

我何時想要在價格訂閱者上調用dep.notify()?我但願在設訂價格時調用它們。在文章的最後,我但願可以進入控制檯並執行:

咱們須要一些方法來掛鉤數據屬性(如價格或數量),因此當它被訪問時咱們能夠將目標保存到咱們的訂閱者數組中,當它被更改時,運行存儲在咱們的訂閱者數組中的函數。

✅ 解決方案:Object.defineProperty()

咱們須要瞭解Object.defineProperty()函數,它是簡單的ES5 JavaScript。它容許咱們爲屬性定義getter和setter函數。在我向您展現如何在Dep類中使用它以前,先簡單展現一下改函數的用法。

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

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

所以,當咱們獲取並設置值時,咱們能夠得到通知。經過一些遞歸,咱們能夠爲數組中的全部項運行它

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

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

🛠 Putting both ideas together

當像這樣的一段代碼運行並得到價格的價值時,咱們但願價格記住這個匿名函數(目標)。這樣,若是價格變化,或者設置爲新值,它將觸發此函數以從新運行,由於它知道此行依賴於它。因此你能夠這樣想。

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

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

或者就咱們的Dep Class而言

Price accessed (get) => 調用dep.depend()來保存當前目標

Price set => 在價格上調用dep.notify(),從新運行全部目標

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

如今看看會發生什麼。

正是咱們所但願的!價格和數量都確實是獲得了實時的響應的!只要價格或數量的價值獲得更新,咱們的總代碼就會從新運行。

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

你看到那個漂亮的紫色數據圈了嗎?看起來應該很眼熟!每一個組件實例都有一個從getter(紅線)收集依賴項的服務觀察器實例(藍色)。當稍後調用設置程序時,它會通知監視程序,它將致使組件從新呈現。下面是我本身的一些註釋的圖片。

是的,如今是否是以爲更有意義了。

顯然,Vue作的可能更復雜更驚喜,但你如今知道了基礎知識。

⏪ 總結:因此咱們學了什麼?

  • 如何建立一個Dep類來收集依賴項(依賴)並從新運行全部依賴項(notify)。
  • 如何建立一個觀察程序來管理咱們正在運行的代碼,這些代碼可能須要做爲依賴項添加(target)。
  • 如何使用Object.defineProperty()建立getter和setter。
相關文章
相關標籤/搜索