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