摘要:遍歷執(zhí)行其中存儲(chǔ)的所有的匿名函數(shù)。如果我們使用一個(gè)類來(lái)管理相關(guān)依賴,這很接近的表現(xiàn)方式代碼看起來(lái)就像下面這樣你會(huì)發(fā)現(xiàn)現(xiàn)在匿名函數(shù)被儲(chǔ)存在而不是原來(lái)的。
許多前端框架(如Angular,React,Vue)都有自己的響應(yīng)式引擎。通過(guò)理解如何響應(yīng),提議提升你的開(kāi)發(fā)能力并能夠更高效地使用JS框架。本文中構(gòu)建的響應(yīng)邏輯與Vue的源碼是一毛一樣的!
響應(yīng)系統(tǒng)初見(jiàn)時(shí),你會(huì)驚訝與Vue的響應(yīng)系統(tǒng)。看看以下面這些簡(jiǎn)單代碼
Price:${{price}}Total:${{price*quantity}}Taxes:${{totalPriceWithTax}}
更新頁(yè)面上的price
重新計(jì)算price與quantity的乘積,更新頁(yè)面
調(diào)用totalPriceWithTax函數(shù)并更新頁(yè)面
等等,你可能會(huì)疑惑為何Vue知道price變化了,它是如何跟蹤所有的變化?
這并非日常的JS編程會(huì)用到的如果你疑惑,那么最大的問(wèn)題是業(yè)務(wù)代碼通常不涉及這些。舉個(gè)例子,如果我運(yùn)行下面代碼:
let price = 5 let quantity = 2 let total = price *quantity price = 20 console.log(`total is ${total}`)
即便我們從未使用過(guò)Vue,我們也能知道會(huì)輸出10。
>> total is 10
更進(jìn)一步,我們想要在price和quantity更新時(shí)
total is 40
遺憾的是,JS是一個(gè)程序,看著它它也不會(huì)變成響應(yīng)式的。這時(shí)我們需要coding
難題我們需要存儲(chǔ)計(jì)算的total,以便在price或quantity變化時(shí),重新運(yùn)行。
解決首先我們需要告知應(yīng)用“下面我要運(yùn)行的代碼先保存起來(lái),我可能在別的時(shí)間還要運(yùn)行!”之后但我們更新代碼中price或quantity的值時(shí),之前存儲(chǔ)的代碼會(huì)被再次調(diào)用。
// save code let total = price * quantity // run code // later on rung store code again
所以通過(guò)記錄函數(shù),可以在變量改變時(shí)多次運(yùn)行:
let price = 5 let quantity = 2 let total = 0 let target = null target = function(){ total = price * quantity } record() // 稍后執(zhí)行 target()
注意target 存儲(chǔ)了一個(gè)匿名函數(shù),不過(guò)如果使用ES6的箭頭函數(shù)語(yǔ)法,我們可以寫(xiě)成這樣:
target = () => { total = price * quantity }
然后我們?cè)俸?jiǎn)單滴定義一下record函數(shù):
let storage = [] //在starage 中存放target函數(shù) function record(){ storage.push(target) }
我們存儲(chǔ)了target(上述例子中就是{ total = price * quantity }),我們?cè)谏院髸?huì)用到它,那時(shí)使用target,就可以運(yùn)行我們記錄的所有函數(shù)。
function target(){ storage.forEach(run => run()) }
遍歷storage執(zhí)行其中存儲(chǔ)的所有的匿名函數(shù)。在代碼中我們可以這樣:
price = 20 console.log(total) // => 10 replay() console.log(total) // => 40
足夠簡(jiǎn)單吧!如果你想看看目前階段完整的代碼,請(qǐng)看:
let price = 5 let quantity = 2 let quantity = 0 let target = null let storage = [] function record () { storage.push(target) } function replay() { storage.forEach(run => run()) } target = () => { total = price * quantity } record() target() price = 20 console.log(total) // => 10 replay() console.log(total) // => 40難題
功能雖然可以實(shí)現(xiàn),但是代碼似乎不夠健壯。我們需要一個(gè)類,來(lái)維護(hù)目標(biāo)列表,在需要重新執(zhí)行時(shí)來(lái)通知執(zhí)行。
解決通過(guò)將所需要的方法封裝成一個(gè)依賴類,通過(guò)這個(gè)類實(shí)現(xiàn)標(biāo)準(zhǔn)的觀察者模式。
如果我們使用一個(gè)類來(lái)管理相關(guān)依賴,(這很接近VUE的表現(xiàn)方式)代碼看起來(lái)就像下面這樣:
class Dep { constructor(){ this.subscribers = [] } depend() { if(target && !this.subscribers.includes(target)){ this.subscribers.push(target) } } notify() { this.subscribers.forEach(sub => sub()) } }
你會(huì)發(fā)現(xiàn)現(xiàn)在匿名函數(shù)被儲(chǔ)存在subscribers而不是原來(lái)的storage。同時(shí),現(xiàn)在的記錄函數(shù)叫做depend而不是record,通知函數(shù)是notify而非replay??纯此麄儓?zhí)行情況:
const dep = new Dep() let price = 5 let quantity = 2 let quantity = 0 let target = () => { total = price * quantity } dep.depend() //將target添加進(jìn)subscribers target() //執(zhí)行獲取total price = 20 console.log(total) // => 10 dep.notify() console.log(total) // => 40
現(xiàn)在代碼的復(fù)用性已經(jīng)初見(jiàn)端倪,但是還有一件別扭的事,我們還需要配置與執(zhí)行目標(biāo)函數(shù)。
難題以后我們會(huì)為每個(gè)變量創(chuàng)建一個(gè)Dep類,對(duì)此我們應(yīng)該使用一個(gè)watcher函數(shù)來(lái)監(jiān)聽(tīng)并更新數(shù)據(jù),而非使用這樣的方式:
let target = () => { total = price * quantity } dep.depend() target()
期望中的代碼應(yīng)該是:
watcher(() => { total = price * quantity })解決 實(shí)現(xiàn)watcher函數(shù)
在watcher函數(shù)中我們做了下面這些事:
function watcher(myFunc){ target = myFunc dep.depend() target() target = null }
如你所見(jiàn),watcher接受一個(gè)myFunc作為參數(shù),將其賦值給全局變量target,并將它添加微訂閱者。在執(zhí)行target后,重置target為下一輪做準(zhǔn)備!
現(xiàn)在只需要這樣的代碼
price = 20 console.log(total) // => 10 dep.notify() console.log(total) // => 40
你可能會(huì)疑惑為什么target是一個(gè)全局變量的形式,而非作為一個(gè)參數(shù)傳入。這個(gè)問(wèn)題在結(jié)尾處會(huì)明朗起來(lái)!
難題現(xiàn)在我們擁有了一個(gè)簡(jiǎn)單的Dep類,但我們真正想要的是每個(gè)變量都能擁有一個(gè)自己的Dep類。先讓我們把之前討論的特性變成一個(gè)對(duì)象吧!
let data = { price: 5,quantity: 2}
我們先假設(shè),每個(gè)屬性都有自己的Dep類:
現(xiàn)在我們運(yùn)行
watcher(() => { totla = data.price * data.quantity })
由于total需要依賴price和quantity兩個(gè)變量,所以這個(gè)匿名函數(shù)需要被寫(xiě)入兩者的subscriber數(shù)組中!
同時(shí)如果我們又有一個(gè)匿名函數(shù),只依賴data.price,那么它僅需要被添加進(jìn)price的dep的subscriber數(shù)組中
但我們改變price的值時(shí),我們期待dep.notify()被執(zhí)行。在文章的最末,我們期待能夠有下面這樣的輸出:
>> total 10 >> price =20 >> total 40
所以現(xiàn)在我們需要去掛載這些屬性(如quantity和price)。這樣當(dāng)其改變時(shí)就會(huì)觸發(fā)subscriber數(shù)組中的函數(shù)。
解決 Object.defineProperty()我們需要了解Object.defineProperty函數(shù)
ES5種提出的,他允許我們?yōu)橐粋€(gè)屬性定義getter與setter函數(shù)。在我們把它和Dep結(jié)合前,我先為你們演示一個(gè)非?;A(chǔ)的用法:
let data = { price: 5,quantity: 2} Object.defineProperty(data,"price",{ get(){ console.log(`Getting price ${internalValue}`); return internalValue } set(newValue){ console.log(`Setting price ${newValue}`); internalValue = newValue } }) total = data.price * data.quantity // 調(diào)用get data.price = 20 // 調(diào)用set
現(xiàn)在當(dāng)我們獲取并設(shè)置值時(shí),我們可以觸發(fā)通知。通過(guò)Object.keys(data)返回對(duì)象鍵的數(shù)組。運(yùn)用一些遞歸,我們可以為數(shù)據(jù)數(shù)組中的所有項(xiàng)運(yùn)行它。
let data = { price: 5,quantity: 2} Object.keys(data).forEach((key) => { let internalValue = data[key] Object.defineProperty(data, key,{ get(){ console.log(`Getting ${key}:${internalValue}`); return internalValue } set(newValue){ console.log(`Setting ${key} to ${newValue}`); internalValue = newValue } }) }) total = data.price * data.quantity data.price = 30
現(xiàn)在你可以在控制臺(tái)上看到:
Getting price: 5 Getting quantity: 20 Setting price to 30成親了
total = data.price * data.quantity
類似上述代碼運(yùn)行后,獲得了price的值。我們還期望能夠記錄這個(gè)匿名函數(shù)。當(dāng)price變化或事被賦予了一個(gè)新值(譯者:感覺(jué)這是一回事)這個(gè)匿名函數(shù)就會(huì)被促發(fā)。
Get => 記住這個(gè)匿名函數(shù),在值變化時(shí)再次執(zhí)行!
Set => 值變了,快去執(zhí)行剛才記下的匿名函數(shù)
就Dep而言:
Price被讀 => 調(diào)用dep.depend()保存當(dāng)前目標(biāo)函數(shù)
Price被寫(xiě) => 調(diào)用dep.notify()去執(zhí)行所有目標(biāo)函數(shù)
好的,現(xiàn)在讓我們將他們合體,并祭出最后的代碼。
let data = {price: 5,quantity: 2} let target = null class Dep { constructor(){ this.subscribers = [] } depend() { if(target && !this.subscribers.includes(target)){ this.subscribers.push(target) } } notify() { this.subscribers.forEach(sub => sub()) } } Object.keys(data).forEach((key) => { let internalValue = data[key] const dep = new Dep() Object.defineProperty(data, key,{ get(){ dep.depend() return internalValue } set(newValue){ internalValue = newValue dep.notify() } }) }) function watcher(myFunc){ target = myFunc target(); target = null; } watch(() => { data.total = data.price * data.quantity })
猜猜看現(xiàn)在會(huì)發(fā)生什么?
>> data.total 10 >> data.price = 20 20 >> data.total 40 >> data.quantity = 3 3 >> data.total 60
正如我們所期待的那樣,price 和 quantity現(xiàn)在是響應(yīng)式的了!當(dāng)price 和 quantity更跟新時(shí),被監(jiān)聽(tīng)函數(shù)會(huì)被重新執(zhí)行!
現(xiàn)在你應(yīng)該可以理解Vue文檔中的這張圖片了吧!
看到圖中紫色數(shù)據(jù)圈getter和setter嗎?看起來(lái)應(yīng)該很熟悉!每個(gè)組件實(shí)例都有一個(gè)watcher實(shí)例(藍(lán)色),它從getter(紅線)收集依賴項(xiàng)。稍后調(diào)用setter時(shí),它會(huì)通知觀察者導(dǎo)致組件重新渲染。下圖是一個(gè)我注釋后的版本。
雖然Vue實(shí)際的代碼愿彼此復(fù)雜,但你現(xiàn)在知道了基本的實(shí)現(xiàn)了。
我們創(chuàng)建一個(gè)Dep類來(lái)收集依賴并重新運(yùn)行所有依賴(notify)
watcher函數(shù)來(lái)將需要監(jiān)聽(tīng)的匿名函數(shù),添加到target
使用Object.defineProperty()去創(chuàng)建getter和setter
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/96416.html
摘要:哪吒別人的看法都是狗屁,你是誰(shuí)只有你自己說(shuō)了才算,這是爹教我的道理。哪吒去他個(gè)鳥(niǎo)命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰(shuí)和你做朋友太乙真人人是否能夠改變命運(yùn),我不曉得。我只曉得,不認(rèn)命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
摘要:案例持續(xù)觸發(fā)事件時(shí),并不立即執(zhí)行函數(shù),當(dāng)毫秒內(nèi)沒(méi)有觸發(fā)事件時(shí),才會(huì)延時(shí)觸發(fā)一次函數(shù)。也以函數(shù)形式暴露普通插槽。這樣的場(chǎng)景組件用函數(shù)式組件是非常方便的。相關(guān)閱讀函數(shù)式組件自定義指令前言 有echarts使用經(jīng)驗(yàn)的同學(xué)可能遇到過(guò)這樣的場(chǎng)景,在window.onresize事件回調(diào)里觸發(fā)echartsBox.resize()方法來(lái)達(dá)到重繪的目的,resize事件是連續(xù)觸發(fā)的這意味著echarts...
摘要:前言非正經(jīng)入門(mén)是相對(duì)正經(jīng)入門(mén)而言的。不過(guò)不要緊,正式學(xué)習(xí)仍需回到正經(jīng)入門(mén)的方式??焖偃腴T(mén)建議先學(xué)會(huì)用拼文寫(xiě)文檔注冊(cè)一個(gè)賬號(hào),把庫(kù)到自己名下,然后用這個(gè)庫(kù)寫(xiě)自己的博客,參見(jiàn)這份介紹。會(huì)用拼文寫(xiě)文章,相當(dāng)于開(kāi)發(fā)已入門(mén)三分之一了。 本系列博文從 Shadow Widget 作者的視角,解釋該框架的設(shè)計(jì)要點(diǎn),既作為用戶手冊(cè)的補(bǔ)充,也從更本質(zhì)角度幫助大家理解 Shadow Widget 為什么這...
閱讀 2806·2019-08-30 15:53
閱讀 590·2019-08-29 17:22
閱讀 1216·2019-08-29 13:10
閱讀 2377·2019-08-26 13:45
閱讀 2806·2019-08-26 10:46
閱讀 3243·2019-08-26 10:45
閱讀 2577·2019-08-26 10:14
閱讀 525·2019-08-23 18:23