--圖片來源vue2.6正式版本(代號:超時空要塞)發佈時,尤雨溪推送配圖。前端
其實這個冷飯我並不想炒,畢竟vue3立刻都要出來。我還在這裏炒冷飯,那明顯就是搞事情。vue
原由:數組
做爲切圖仔搬磚汪,長期切圖jq一把梭。重複繁瑣的切圖,讓本身陷入了一個無限的圍城。想出去切圖這個圍城看一看,可是又懼怕由於切圖時間久了,本身會的也只有切圖了。app
爲了後面可以繼續搬磚恰飯,幫助本身跳出切圖仔的圍城。也去看了vue相關文檔,當時記憶深入以爲還行。但是G胖這個時候發動小紫本和打折魔咒,不知不覺又沉迷於DOTA小本子上面了。關於vue響應式原理很快忘得一塌糊塗,只記得一個屬性Object.defindProperty
,而後就沒有而後了......函數
爲了不本身後面再次忘記,因此這裏炒一個冷飯加深記憶。優化
在講解vue響應式的原理以前,讓咱們來一段Vue代碼做爲示例:this
<div id="app"> <div>主食: {{ food }}</div> <div>飲料: {{ drink }}</div> <div>菜單: {{ menu }}</div> </div> <script> let vue = new Vue({ el: '#app', data: { food: '煎餅果子', drink: '熱豆漿' }, computed: { menu() { return this.food + this.drink } } }) </script>
當food
和drink
發生變化後,Vue會作兩件事:3d
在頁面上更新food
和drink
的值。code
再次調用menu
, 從新計算food + drink
的值, 並在頁面上面更新。對象
更新值+計算值作的事情其實很簡單,幾行代碼的事情。問題是當food
或者drink
變化時,Vue是怎麼知道誰變化,而後立刻響應其行爲,去執行那"簡單的幾行代碼"?
因此,當看到Vue案例時,詞窮的我當時第一反應就是牛皮
。
之因此發出感嘆,是由於一般的JavaScript代碼是實現不了這樣的功能的。話很少說,讓咱們直接上代碼來講明:
let food = "煎餅果子" let drink = "熱豆漿" let menu = null menu = food + drink food = '炸雞漢堡' drink = '快樂水' console.log(menu)
最終控制檯打印結果:
煎餅果子熱豆漿
若是是在Vue當中,food
和drink
發生了變化,那麼Vue會跟着作出響應動做,從而在控制檯輸出咱們想要的結果:
炸雞漢堡快樂水
這裏就出現第一個問題,當food
或者drink
發生變化以後,menu
並不會響應其變化。這個時候就須要咱們來解決這個問題,知足menu
響應。
借鑑Vue同樣,咱們先把menu
的計算方法。也寫成一個函數,取名爲target
。而後每次food
或者drink
變化的時候調用target
函數
let food = "煎餅果子" let drink = "熱豆漿" let menu = null let target = () => { menu = food + drink } target() // 初始化菜單menu food = '炸雞漢堡' drink = '快樂水' target() console.log(menu)
控制檯輸出:
炸雞漢堡快樂水
前面一把梭直接調用的知足menu
響應的問題,可是也間接留下一個新的疑惑點。這裏針對一個菜單,就寫了一個target。假設有多個菜單須要響應呢?
例如:
單人早餐 = 煎餅果子 + 熱豆漿
豪華套餐: 煎餅果子加兩雞蛋 + 熱豆漿 + 油條一根午飯
若是這個時候切換成:
單人午飯 = 炸雞漢堡 + 快樂水
豪華套餐: 雙層炸雞漢堡 + 快樂水 + 快樂薯條一包
按照前面的邏輯, 估計得寫N個target。這個時候響應式又是一個麻煩事情,但是有句話說的好。梭哈一時爽,一直梭哈一直爽。
既然前面直接採用target一把梭完成,因此針對N個target方法,我也能夠直接來個for循環一把梭能完成響應式問題。
let storge = [] // 用來存儲target function record (){ // storge.push(target) }
data
有變動。就調用這個函數,進行一把for
循環.function replay (){ storge.forEach(run => run()) }
let food = "煎餅果子" let drink = "熱豆漿" let menu = null food = '炸雞漢堡' drink = '快樂水' let target = () => { menu = food + drink } let storge = []; //用來存儲更多的target function record(target) { storge.push(target) } function replay() { storge.forEach(run => run()) } record(target) replay() food = '炸雞漢堡' drink = '快樂水' replay() console.log(menu)
最後控制檯成功輸出:
炸雞漢堡快樂水
經過一把梭實現功能,那麼接下來就開始思考優化部分了。繼續記錄target這類的代碼,這樣有點怪怪的。爲了後面方便管理,咱們把代碼進行簡單的優化,封裝成一個類:
class Dep { constructor() { this.subs = [] } // 收集依賴 depend(sub) { if (sub && !this.subs.includes(sub)) { // 作一個判斷 this.subs.push(sub) } } notify() { console.log("暗號:下雨啦,收衣服啦!") this.subs.forEach(sub => sub()) // 運行咱們的target } }
就這樣target
函數存儲在類的subs
中,record
也變成了depend
,使用notify
來代替replay
封裝成類以後,每次當data數據更新的時候,就會發出一個暗號下雨啦,收衣服啦!
而後就開始遍歷運行相應的target
依賴了。
新的調用代碼就更加清晰明瞭:
let dep = new Dep() let food = "煎餅果子" let drink = "熱豆漿" let menu = null let target = () => { menu = food + drink } dep.depend(target) target() // 完成menu第一次初始化 console.log(menu) food = '炸雞漢堡' drink = '快樂水' dep.notify() console.log(menu)
控制檯輸出:
煎餅果子熱豆漿 暗號:'下雨啦,收衣服啦!' 炸雞漢堡快樂水
當前的代碼,是肯定一個依賴事件,就定義target
,而後調用依賴類dep.depend
將其存儲起來。
let target = () => { menu = food + drink } dep.depend(target) target()
這個時候又新來一個target
事件又該如何作:
新添加一個target
事件?
let target2 = () => { 新的依賴事件 } dep.depend(target2) target2()
要是有幾百個依賴,那還不得上天。我估計要是這樣寫代碼,估計你的同事要說你寫代碼像CXK
借鑑觀察者模式,封裝一個watcher
函數. 幫你觀察記錄相關target
事件,避免屢次聲明變量。
function watcher(myFun) { target = myFun dep.depend(target) target() target = null } watcher(() => { menu = food + drink })
正如你所看到的,watcher
函數接受myFunc
參數,將其賦給全局的target
上,調用dep.depend()
將其添加到數組裏,以後調用並重置target
。
既然又封裝一個新的函數,那麼驗證又將是必不可少的了。這裏咱們修改一下drink
來試試:
drink = "快樂水" console.log(menu) dep.notify() console.log(menu)
控制檯輸出結果:
煎餅果子熱豆漿 暗號:下雨啦,收衣服啦! 煎餅果子快樂水
鋪墊了這麼久,一個關鍵性角色這個時候也登場了。
該方法容許精確添加或修改對象的屬性。經過賦值操做添加的普通屬性是可枚舉的,可以在屬性枚舉期間呈現出來(for...in 或 Object.keys 方法), 這些屬性的值能夠被改變,也能夠被刪除。這個方法容許修改默認的額外選項(或配置)。默認狀況下,使用 Object.defineProperty() 添加的屬性值是不可修改的。
--《MDN文檔》
不明覺厲? 那就先熱身一下,進入快樂的舉例子環節:
let data = { food: '煎餅果子', drink: '熱豆漿' } Object.defineProperty(data, 'food', { get() { console.log(`觸發get方法`) }, set(newVal) { console.log(`設置food爲${newVal}`) } }) data.food data.food = 炸雞漢堡
控制檯輸出:
觸發get方法 設置food爲炸雞漢堡
可是僅僅憑藉object.defineProperty
是沒法完成當一個數據更新了,完成數據響應。並且代碼這裏也是隻是對food
作了一個處理, 還有drink
沒有處理,因此爲了完成data因此屬性都作相應的處理。接下來就是對於Object.defineProperty()
進行簡單的封裝處理了:
Object.keys(data).forEach(key => { let value = data[key] Object.defineProperty(data, key, { get() { return value }, set(newVal) { value = newVal } }) })
遍歷了data每一個屬性,而後對每一個屬性進行偵聽。這樣data的屬性一旦改變,就會自動發出通知.
前面零零散散分別講了 Dep
、watcher
和object.defineProperty
, 那麼接下來就讓咱們把這個幾個部分整合到一塊兒,完整查看整個代碼:
let data = { food: '煎餅果子', drink: '熱豆漿' } class Dep { constructor() { this.subs = [] } // 收集依賴 depend(sub) { if (sub && !this.subs.includes(sub)) { // 作一個判斷 this.subs.push(sub) } } notify() { console.log("暗號:下雨啦,收衣服啦!") this.subs.forEach(sub => sub()) // 運行咱們的target } } Object.keys(data).forEach(key => { let value = data[key] let dep = new Dep() Object.defineProperty(data, key, { get() { dep.depend(target) return value }, set(newVal) { value = newVal dep.notify() } }) }) function watcher(myFun) { target = myFun // dep.depend(target) 這裏修改,移動到Object.defineProperty當中去 target() target = null } watcher(() => { data.menu = data.food + data.drink }) console.log(data.menu) data.food = "炸雞漢堡" data.drink = "快樂水" console.log(data.menu)
控制檯輸出:
煎餅果子熱豆漿 暗號:下雨啦,收衣服啦! 炸雞漢堡快樂
這裏徹底實現了文章開頭所提出的需求,每當food
或drink
更新時,咱們的menu
也會跟着響應並更新。
這時候Vue文檔的插圖的意義就很明顯了:
以上就是個人炒冷飯內容,怕忘記重寫總結一下,有說錯的地方多擔待。(特拿前端勸退師騷聲明一份,窺伺很久了。)
意思就是寫得略粗糙,別噴我。。。
我是車大棒,我爲我本身插眼。