自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

以Vue為例,解釋JavaScript的反應(yīng)性

開發(fā) 前端
很多前端 JavaScript 框架(如 Angular、React 和 Vue)都有自己的反應(yīng)性(Reactivity)引擎。理解反應(yīng)式是什么以及如何運行能夠提升你的開發(fā)水平,同時能夠更高效地使用 JavaScript。在本文中,我們構(gòu)建了與 Vue 源碼相同的反應(yīng)性功能。

[[241065]]

很多前端 JavaScript 框架(如 Angular、React 和 Vue)都有自己的反應(yīng)性(Reactivity)引擎。理解反應(yīng)式是什么以及如何運行能夠提升你的開發(fā)水平,同時能夠更高效地使用 JavaScript。在本文中,我們構(gòu)建了與 Vue 源碼相同的反應(yīng)性功能。

反應(yīng)性系統(tǒng)

當(dāng)你第一次見到 Vue 的反應(yīng)性系統(tǒng)時,你可能會感覺有些神奇。以下面這個簡單的 Vue 應(yīng)用為例:

 

不知道基于什么原因,Vue 能夠知道price的值是否發(fā)生了變化,并且在變化的時候能夠完成如下三件事情:

  • 更新 Web 頁面price的值;
  • 重新計算乘法表達(dá)式price * quantity,并更新頁面;
  • 再次調(diào)用totalPriceWithTax函數(shù)并更新頁面。

但是,稍等,我似乎聽到你想問,Vue 如何知道在price發(fā)生變化的時候都要更新哪些值,它又是如何跟蹤所有的內(nèi)容的呢?

 

這并不是 JavaScript 編程通常的運行方式。

如果這對你來說不那么直觀,那么我們需要明白程序通常并不是按照這種方式來運行的。例如,如果我運行下面的樣例代碼:

 

你猜將會打印出什么內(nèi)容呢?因為我們沒有使用 Vue,它將會打印出10:

 

在 Vue 中,我們想要在price或quantity更新的時候,total也進(jìn)行更新。我們希望的輸出是:

 

但令人遺憾的是,JavaScript 是過程性的,并不是反應(yīng)式的,所以在現(xiàn)實代碼并這并不可行。為了讓total具有反應(yīng)性,我們必須讓 JavaScript 語言按照不同的方式來運行。

問題

我們需要記住如何計算total,這樣才能在price或quantity發(fā)生變化的時候重新運行。

解決方案

首先,我們需要有某種方法告訴我們的應(yīng)用,“我將要運行的代碼是什么,將它存儲起來,在稍后某個時間點我可能需要你運行它”。然后,我們運行代碼,在price或quantity變量發(fā)生變化的時候,再次運行存儲的代碼:

 

我們想到的辦法可能就是將函數(shù)的內(nèi)容記錄下來,這樣就能再次運行了:

 

需要注意,我們在target變量中存儲了一個匿名函數(shù),然后調(diào)用了record函數(shù)。如果采用 ES6 的箭頭語法的話,我還可以寫成如下的形式:

 

record的定義非常簡單:

 

我們將target存儲了起來(我們的示例中也就是{ total = price * quantity }),這樣的話,我們就能在隨后運行它,可能會借助一個replay函數(shù)運行我們記錄下來的所有內(nèi)容。

 

這樣會遍歷我們在storage數(shù)組中存儲的所有匿名函數(shù),并運行它們。

那么在我們的代碼中,只需:

 

非常簡單,對吧?如果你想要通讀代碼并再次嘗試的話,下面給出了完整的代碼。

 

問題

我們可以按需繼續(xù)記錄 target,但是更好的方式是有一種健壯的方案,能夠擴展我們的應(yīng)用。我們可以使用一個類,讓這個類維護(hù)一個 target 的列表,當(dāng)需要它們重新運行的時候,這個類會得到通知。

解決方案:依賴類

要解決這個問題,我們將這些行為封裝到單獨的類中,使用一個依賴類(Dependency Class)來實現(xiàn)標(biāo)準(zhǔn)的觀察者模式編程。

如果我們創(chuàng)建 JavaScript 類來管理依賴的話(類似于 Vue 的處理方式),它看起來可能會如下所示:

 

需要注意,我們這里不再使用storage,而是使用subscribers來存儲匿名函數(shù),也不再使用record函數(shù)了,而是調(diào)用depend,同時使用notify代替了replay。要讓它運行起來,只需:

 

它依然可以運行,而且我們的代碼看上去具備了一定的可重用性。唯一感覺尤其詭異的地方就是設(shè)置和運行target。

問題

未來,每個類都會有一個 Dep 類,如果能將創(chuàng)建匿名函數(shù)觀察更新的行為封裝起來就更好了。接下來,watcher函數(shù)將會出場來負(fù)責(zé)這種行為。

所以,我們將不會再調(diào)用:

 

(這就是上面示例的代碼)

相反,我們只需這樣調(diào)用:

 

解決方案:Watcher 函數(shù)

在 Watcher 函數(shù)中,我們可以做幾件很簡單的事情:

 

可以看到,watcher函數(shù)接受一個myFunc變量,將其作為我們的全局target屬性,調(diào)用dep.depend(),將會以訂閱者的形式添加我們的 target,調(diào)用target函數(shù)并重置target。

現(xiàn)在,我們可以運行下面的代碼:

你可能會想,我們?yōu)槭裁匆獙arget實現(xiàn)為全局變量,而不是將其傳遞給所需的函數(shù)。這里有一定的原因,在本文結(jié)束的時候,相信你就明白了。

問題

我們現(xiàn)在有了一個Dep類,但是我們真正想要實現(xiàn)的是每個變量都有自己的 Dep。在進(jìn)行下一步講解之前,我們先將它們放到屬性中。

 

我們先假設(shè)每個屬性(price和quantity)都有其自己的內(nèi)部 Dep 類。

 

現(xiàn)在,當(dāng)我們運行:

 

因為訪問到了data.price的值,所以我希望price屬性的 Dep 類要將我們的匿名函數(shù)(存儲在target中)放到它的訂閱數(shù)組中(通過調(diào)用dep.depend())。因為data.quantity也被訪問到了,所以我希望quantity屬性的 Dep 類要將該匿名函數(shù)(存儲在target中)放到它的訂閱數(shù)組中:

 

如果我還有其他的匿名函數(shù)只訪問data.price的話,我希望要將這個函數(shù)放到price屬性的 Dep 類中。

 

那么,我該在何時為price的訂閱者調(diào)用dep.notify()呢?答案是為price賦值的時候。在本文結(jié)束的時候,我希望能夠在命令行中實現(xiàn)如下的效果:

 

我們希望能有某種方式嵌入到數(shù)據(jù)屬性中(price或quantity),這樣的話,當(dāng)屬性被訪問的時候,能夠?qū)arget存儲到訂閱者數(shù)組中,當(dāng)屬性變更時,能夠運行存儲在訂閱者數(shù)組中的函數(shù)。

解決方案:Object.defineProperty()

我們需要學(xué)習(xí) ES 5 JavaScript 所提供 Object.defineProperty() 函數(shù)。它允許我們?yōu)閷傩远x getter 和 setter 函數(shù)。在展示如何與 Dep 類協(xié)作之前,我們看一下它的基礎(chǔ)用法。

  

可以看到,這里只是打印了兩條日志。但是,它并沒有實際get和set值,這是因為我們將功能覆蓋掉了?,F(xiàn)在,我們將功能添加回來。get()預(yù)期要返回一個值,而set()依然要更新值,所以我們添加一個internalValue變量來存儲當(dāng)前的price值。

 

我們的get和set都能正常運行了,你覺得控制臺的打印信息會是什么呢?

 

所以,當(dāng)取值和設(shè)置值的時候,我們有了一種得到提醒的方法。借助一些遞歸,我們就可以將其用到數(shù)據(jù)數(shù)組的所有條目中了。

值得一提的是,Object.keys(data)能夠返回對象中 key 所組成的數(shù)組。

 

現(xiàn)在,所有的屬性都有 getter 和 setter 了,我們來看一下控制臺:

 

 將這兩個理念組合在一起

 

當(dāng)這樣的代碼運行并嘗試 get price屬性的值時,我們希望price能夠記住這個匿名函數(shù)(target)。通過這種方式,如果price發(fā)生了變化,或者被set了一個新的值,這個函數(shù)就能重新運行,因為它能夠知道這行代碼依賴該屬性。所以,你可以按照如下的方式來思考。

Get=>記住該匿名函數(shù),當(dāng)值發(fā)生變化的時候我們會重新運行。

Set=>運行保存的匿名函數(shù),我們的值就會發(fā)生變化。

或者,在 Dep Class 的場景下:

Price 訪問 (get) =>調(diào)用dep.depend()保存當(dāng)前的target;

Price set =>調(diào)用 price 的dep.notify(),重新運行所有的target。

接下來,我們將這兩個理念組合起來,并看一下最終的代碼。

 

在我們運行的時候,看一下控制臺的輸出:

 

完全符合我們的預(yù)期!現(xiàn)在price和quantity都是反應(yīng)式的了。當(dāng)price或quantity的值更新時,我們的代碼完全重新運行了。

Vue 文檔中的圖示對你來說應(yīng)該就非常清晰了。

 

看到漂亮的 Data 圓圈中的 getters 和 setters 了嗎?它看起來似曾相識!每個組件實例都有一個watcher(藍(lán)色圓圈),它會從 getter 中收集依賴(紅線)。當(dāng) setter 隨后被調(diào)用時,它會 通知watcher,從而會導(dǎo)致組件的重新渲染。如下的圖片添加了一些我自己的注釋。

 

現(xiàn)在,是不是感覺一目了然了呢?

當(dāng)然,Vue 底層的處理要更復(fù)雜,但是你現(xiàn)在已經(jīng)掌握了它的基礎(chǔ)。

我們學(xué)到了什么呢?

  • 如何創(chuàng)建 Dep 來收集依賴(depend)并重新運行所有的依賴(notify);
  • 如何創(chuàng)建 watcher 來管理我們正在運行的代碼,這些代碼可能需要作為依賴添加進(jìn)來(target);
  • 如何使用 Object.defineProperty() 來創(chuàng)建 getter 和 setter。

原文鏈接

https://medium.com/vue-mastery/the-best-explanation-of-javascript-reactivity-fea6112dd80d 

責(zé)任編輯:龐桂玉 來源: 前端之巔
相關(guān)推薦

2021-08-02 09:50:47

Vetur源碼SMART

2016-12-20 12:34:46

存儲MySQL流程

2021-05-31 08:00:00

消息隊列架構(gòu)Rabbit MQ

2009-03-02 16:57:34

LinuxUbuntu配置完全方案

2011-07-08 09:55:02

數(shù)據(jù)中心防震

2023-06-26 08:43:57

OracleTRACE葉節(jié)點

2022-02-14 14:28:57

驅(qū)動開發(fā)鴻蒙系統(tǒng)

2021-04-16 08:20:00

Flink CEP直播監(jiān)控

2022-01-10 12:23:00

TypeScript ESLint前端

2021-01-14 09:00:00

開發(fā)FedoraUbuntu

2021-02-23 06:55:09

npmVue工具

2018-10-17 08:39:56

Redis分布式系統(tǒng)緩存

2016-12-05 14:03:07

Flink大數(shù)據(jù)

2018-01-02 09:00:51

大數(shù)據(jù)營銷王者榮耀

2016-12-06 20:03:48

Flink流處理謬見

2011-08-04 09:57:03

dbmonsterMySQL

2015-07-01 15:39:52

Ceph云存儲NAS

2017-11-02 14:46:18

JavaScriptTypeScript遍歷

2013-02-18 10:12:58

Apache服務(wù)器訪問動態(tài)網(wǎng)站

2010-09-07 10:01:47

網(wǎng)游服務(wù)器搭建方案
點贊
收藏

51CTO技術(shù)棧公眾號