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

深入理解vue響應(yīng)式原理

原創(chuàng)
開(kāi)發(fā)
Vue 最獨(dú)特的特性之一,是其非侵入性的響應(yīng)式系統(tǒng)。數(shù)據(jù)模型僅僅是普通的 JavaScript 對(duì)象。而當(dāng)你修改它們時(shí),視圖會(huì)進(jìn)行更新。這使得狀態(tài)管理非常簡(jiǎn)單直接,不過(guò)理解其工作原理同樣重要,這樣你可以避開(kāi)一些常見(jiàn)的問(wèn)題。----官方文檔 本文將針對(duì)響應(yīng)式原理做一個(gè)詳細(xì)介紹,并且?guī)銓?shí)現(xiàn)一個(gè)基礎(chǔ)版的響應(yīng)式系統(tǒng)。

【51CTO.com原創(chuàng)稿件】前言

Vue 最獨(dú)特的特性之一,是其非侵入性的響應(yīng)式系統(tǒng)。數(shù)據(jù)模型僅僅是普通的 JavaScript 對(duì)象。而當(dāng)你修改它們時(shí),視圖會(huì)進(jìn)行更新。這使得狀態(tài)管理非常簡(jiǎn)單直接,不過(guò)理解其工作原理同樣重要,這樣你可以避開(kāi)一些常見(jiàn)的問(wèn)題。----官方文檔 本文將針對(duì)響應(yīng)式原理做一個(gè)詳細(xì)介紹,并且?guī)銓?shí)現(xiàn)一個(gè)基礎(chǔ)版的響應(yīng)式系統(tǒng)。本文的代碼請(qǐng)猛戳Github博客

[[269278]]

什么是響應(yīng)式

我們先來(lái)看個(gè)例子:

  1. <div id="app"
  2.     <div>Price :¥{{ price }}</div> 
  3.     <div>Total:¥{{ price * quantity }}</div> 
  4.     <div>Taxes: ¥{{ totalPriceWithTax }}</div> 
  5.     <button @click="changePrice">改變價(jià)格</button> 
  6. </div> 
  1. var app = new Vue({ 
  2.   el: '#app'
  3.   data() { 
  4.     return { 
  5.       price: 5.0, 
  6.       quantity: 2 
  7.     }; 
  8.   }, 
  9.   computed: { 
  10.     totalPriceWithTax() { 
  11.       return this.price * this.quantity * 1.03; 
  12.     } 
  13.   }, 
  14.   methods: { 
  15.     changePrice() { 
  16.       this.price = 10; 
  17.     } 
  18.   } 
  19. }) 

上例中當(dāng)price 發(fā)生變化的時(shí)候,Vue就知道自己需要做三件事情:

  • 更新頁(yè)面上price的值
  • 計(jì)算表達(dá)式 price*quantity 的值,更新頁(yè)面
  • 調(diào)用totalPriceWithTax 函數(shù),更新頁(yè)面

發(fā)生變化后,會(huì)重新對(duì)頁(yè)面渲染,這就是Vue響應(yīng)式,那么這一切是怎么做到的呢?

想完成這個(gè)過(guò)程,我們需要:

  • 偵測(cè)數(shù)據(jù)的變化
  • 收集視圖依賴了哪些數(shù)據(jù)
  • 數(shù)據(jù)變化時(shí),自動(dòng)“通知”需要更新的視圖部分,并進(jìn)行更新

對(duì)應(yīng)專(zhuān)業(yè)俗語(yǔ)分別是:

  • 數(shù)據(jù)劫持 / 數(shù)據(jù)代理
  • 依賴收集
  • 發(fā)布訂閱模式

如何偵測(cè)數(shù)據(jù)的變化

首先有個(gè)問(wèn)題,在Javascript中,如何偵測(cè)一個(gè)對(duì)象的變化? 其實(shí)有兩種辦法可以偵測(cè)到變化:使用Object.defineProperty和ES6的Proxy,這就是進(jìn)行數(shù)據(jù)劫持或數(shù)據(jù)代理。這部分代碼主要參考珠峰架構(gòu)課。

方法1.Object.defineProperty實(shí)現(xiàn)

Vue通過(guò)設(shè)定對(duì)象屬性的 setter/getter 方法來(lái)監(jiān)聽(tīng)數(shù)據(jù)的變化,通過(guò)getter進(jìn)行依賴收集,而每個(gè)setter方法就是一個(gè)觀察者,在數(shù)據(jù)變更的時(shí)候通知訂閱者更新視圖。

  1. function render () { 
  2. console.log('模擬視圖渲染'
  3. let data = { 
  4. name'浪里行舟'
  5. location: { x: 100, y: 100 } 
  6. observe(data) 
  7. function observe (obj) { 
  8. // 判斷類(lèi)型 
  9. if (!obj || typeof obj !== 'object') { 
  10. return 
  11. Object.keys(obj).forEach(key => { 
  12. defineReactive(obj, key, obj[key]) 
  13. }) 
  14. function defineReactive (obj, key, value) { 
  15. // 遞歸子屬性 
  16. observe(value) 
  17. Object.defineProperty(obj, key, { 
  18. enumerable: true, //可枚舉(可以遍歷) 
  19. configurable: true, //可配置(比如可以刪除) 
  20. get: function reactiveGetter () { 
  21. console.log('get', value) // 監(jiān)聽(tīng) 
  22. return value 
  23. }, 
  24. setfunction reactiveSetter (newVal) {  
  25. observe(newVal) //如果賦值是一個(gè)對(duì)象,也要遞歸子屬性  
  26. if (newVal !== value) { 
  27. console.log('set', newVal) // 監(jiān)聽(tīng) 
  28. render()  
  29. value = newVal  
  30. }  
  31. })  
  32. }  
  33. }  
  34. data.location = {  
  35. x: 1000,  
  36. y: 1000  
  37. } //set {x: 1000,y: 1000} 模擬視圖渲染  
  38. data.name // get 浪里行舟 

幾個(gè)注意點(diǎn)補(bǔ)充說(shuō)明:

  • 這種方式無(wú)法檢測(cè)到對(duì)象屬性的添加或刪除(如data.location.a=1)。

這是因?yàn)?Vue 通過(guò)Object.defineProperty來(lái)將對(duì)象的key轉(zhuǎn)換成getter/setter的形式來(lái)追蹤變化,但getter/setter只能追蹤一個(gè)數(shù)據(jù)是否被修改,無(wú)法追蹤新增屬性和刪除屬性。如果是刪除屬性,我們可以用vm.$delete實(shí)現(xiàn),那如果是新增屬性,該怎么辦呢? 1)可以使用 Vue.set(location, a, 1) 方法向嵌套對(duì)象添加響應(yīng)式屬性; 2)也可以給這個(gè)對(duì)象重新賦值,比如data.location = {...data.location,a:1}

  • Object.defineProperty 不能監(jiān)聽(tīng)數(shù)組的變化,需要進(jìn)行數(shù)組方法的重寫(xiě)
  1. function render() { 
  2. console.log('模擬視圖渲染'
  3. let obj = [1, 2, 3] 
  4. let methods = ['pop''shift''unshift''sort''reverse''splice''push'
  5. // 先獲取到原來(lái)的原型上的方法 
  6. let arrayProto = Array.prototype 
  7. // 創(chuàng)建一個(gè)自己的原型 并且重寫(xiě)methods這些方法 
  8. let proto = Object.create(arrayProto)  
  9. methods.forEach(method => {  
  10. proto[method] = function() {  
  11. // AOP  
  12. arrayProto[method].call(this, ...arguments) 
  13. render()  
  14. }  
  15. }) 
  16. function observer(obj) {  
  17. // 把所有的屬性定義成set/get的方式  
  18. if (Array.isArray(obj)) {  
  19. obj.__proto__ = proto  
  20. return  
  21. }  
  22. if (typeof obj == 'object') {  
  23. for (let key in obj) {  
  24. defineReactive(obj, key, obj[key])  
  25. }  
  26. }  
  27. }  
  28. function defineReactive(data, key, value) {  
  29. observer(value)  
  30. Object.defineProperty(data, key, {  
  31. get() {  
  32. return value  
  33. },  
  34. set(newValue) { 
  35. observer(newValue) 
  36. if (newValue !== value) {  
  37. render()  
  38. value = newValue  
  39. }  
  40. })  
  41. }  
  42. observer(obj)  
  43. function $set(data, key, value) { 
  44. defineReactive(data, key, value)  
  45. }  
  46. obj.push(123, 55)  
  47. console.log(obj) //[1, 2, 3, 123, 55] 

這種方法將數(shù)組的常用方法進(jìn)行重寫(xiě),進(jìn)而覆蓋掉原生的數(shù)組方法,重寫(xiě)之后的數(shù)組方法需要能夠被攔截。但有些數(shù)組操作Vue時(shí)攔截不到的,當(dāng)然也就沒(méi)辦法響應(yīng),比如:

  1. obj.length-- // 不支持?jǐn)?shù)組的長(zhǎng)度變化 
  2.  
  3. obj[0]=1 // 修改數(shù)組中***個(gè)元素,也無(wú)法偵測(cè)數(shù)組的變化 

ES6提供了元編程的能力,所以有能力攔截,Vue3.0可能會(huì)用ES6中Proxy 作為實(shí)現(xiàn)數(shù)據(jù)代理的主要方式。

方法2.Proxy實(shí)現(xiàn)

Proxy 是 JavaScript 2015 的一個(gè)新特性。Proxy 的代理是針對(duì)整個(gè)對(duì)象的,而不是對(duì)象的某個(gè)屬性,因此不同于 Object.defineProperty 的必須遍歷對(duì)象每個(gè)屬性,Proxy 只需要做一層代理就可以監(jiān)聽(tīng)同級(jí)結(jié)構(gòu)下的所有屬性變化,當(dāng)然對(duì)于深層結(jié)構(gòu),遞歸還是需要進(jìn)行的。此外**Proxy支持代理數(shù)組的變化。**

  1. function render() {  
  2. console.log('模擬視圖的更新')  
  3. }  
  4. let obj = {  
  5. name'前端工匠',  
  6. age: { age: 100 },  
  7. arr: [1, 2, 3]  
  8. }  
  9. let handler = { 
  10. get(target, key) { 
  11. // 如果取的值是對(duì)象就在對(duì)這個(gè)對(duì)象進(jìn)行數(shù)據(jù)劫持  
  12. if (typeof target[key] == 'object' && target[key] !== null) { 
  13. return new Proxy(target[key], handler)  
  14. return Reflect.get(target, key)  
  15. },  
  16. set(target, key, value) {  
  17. if (key === 'length'return true  
  18. render()  
  19. return Reflect.set(target, key, value)  
  20. }  
  21. }  
  22. let proxy = new Proxy(obj, handler)  
  23. proxy.age.name = '浪里行舟' // 支持新增屬性  
  24. console.log(proxy.age.name) // 模擬視圖的更新 浪里行舟  
  25. proxy.arr[0] = '浪里行舟' //支持?jǐn)?shù)組的內(nèi)容發(fā)生變化 
  26. console.log(proxy.arr) // 模擬視圖的更新 ['浪里行舟', 2, 3 ] 
  27. proxy.arr.length-- // 無(wú)效 

以上代碼不僅精簡(jiǎn),而且還是實(shí)現(xiàn)一套代碼對(duì)對(duì)象和數(shù)組的偵測(cè)都適用。不過(guò)Proxy兼容性不太好!

我們之所以要觀察數(shù)據(jù),其目的在于當(dāng)數(shù)據(jù)的屬性發(fā)生變化時(shí),可以通知那些曾經(jīng)使用了該數(shù)據(jù)的地方。比如***例子中,模板中使用了price 數(shù)據(jù),當(dāng)它發(fā)生變化時(shí),要向使用了它的地方發(fā)送通知。那如何收集依賴呢?

收集依賴與發(fā)布訂閱模式

如何收集依賴,總結(jié)起來(lái)就一句話,在getter中收集依賴,在setter中觸發(fā)依賴 我們先來(lái)實(shí)現(xiàn)一個(gè) Dep 類(lèi),用于解耦屬性的依賴收集和派發(fā)更新操作。

  1. // 通過(guò) Dep 解耦屬性的依賴和更新操作 
  2. class Dep { 
  3. constructor() { 
  4. this.subs = [] 
  5. // 添加依賴  
  6. addSub(sub) {  
  7. this.subs.push(sub)  
  8. }  
  9. // 更新  
  10. notify() {  
  11. this.subs.forEach(sub => {  
  12. sub.update()  
  13. })  
  14. }  
  15. }  
  16. // 全局屬性,通過(guò)該屬性配置 Watcher  
  17. Dep.target = null 

當(dāng)需要依賴收集的時(shí)候調(diào)用 addSub,當(dāng)需要派發(fā)更新的時(shí)候調(diào)用 notify。具體如何調(diào)用呢?

  1. let dp = new Dep()  
  2. dp.addSub(() => {  
  3. console.log('emit here')  
  4. })  
  5. dp.notify() 

這就是一個(gè)簡(jiǎn)單實(shí)現(xiàn)的“事件發(fā)布訂閱模式”,當(dāng)然代碼只是啟發(fā)思路,真實(shí)應(yīng)用還比較“粗糙”,沒(méi)有進(jìn)行事件名設(shè)置,APIs 也并不豐富,但完全能夠說(shuō)明問(wèn)題了。

接下來(lái)我們先來(lái)簡(jiǎn)單的了解下 Vue 組件掛載時(shí)添加響應(yīng)式的過(guò)程。在組件掛載時(shí),會(huì)先對(duì)所有需要的屬性調(diào)用 Object.defineProperty(),然后實(shí)例化 Watcher,傳入組件更新的回調(diào)。在實(shí)例化過(guò)程中,會(huì)對(duì)模板中的屬性進(jìn)行求值,觸發(fā)依賴收集。我們可以把Watcher理解成一個(gè)中介的角色,數(shù)據(jù)發(fā)生變化時(shí)通知它,然后它再通知其他地方。

***需要對(duì) defineReactive 函數(shù)進(jìn)行改造,在自定義函數(shù)中添加依賴收集和派發(fā)更新相關(guān)的代碼。

  1. function render () { 
  2.   console.log('模擬視圖渲染'
  3. let data = { 
  4.   name'浪里行舟'
  5.   location: { x: 100, y: 100 } 
  6. observe(data) 
  7.   let dp = new Dep() 
  8. function observe (obj) { 
  9.   // 判斷類(lèi)型 
  10.   if (!obj || typeof obj !== 'object') { 
  11.     return 
  12.   } 
  13.   Object.keys(obj).forEach(key => { 
  14.     defineReactive(obj, key, obj[key]) 
  15.   }) 
  16.   function defineReactive (obj, key, value) { 
  17.     // 遞歸子屬性 
  18.     observe(value) 
  19.     Object.defineProperty(obj, key, { 
  20.       enumerable: true, //可枚舉(可以遍歷) 
  21.       configurable: true, //可配置(比如可以刪除) 
  22.       get: function reactiveGetter () { 
  23.         console.log('get', value) // 監(jiān)聽(tīng) 
  24.     // 將 Watcher 添加到訂閱 
  25.        if (Dep.target) { 
  26.          dp.addSub(Dep.target) 
  27.        } 
  28.         return value 
  29.       }, 
  30.       setfunction reactiveSetter (newVal) { 
  31.         observe(newVal) //如果賦值是一個(gè)對(duì)象,也要遞歸子屬性 
  32.         if (newVal !== value) { 
  33.           console.log('set', newVal) // 監(jiān)聽(tīng) 
  34.           render() 
  35.           value = newVal 
  36.      // 執(zhí)行 watcher 的 update 方法 
  37.           dp.notify() 
  38.         } 
  39.       } 
  40.     }) 
  41.   } 

以上所有代碼實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的數(shù)據(jù)響應(yīng)式,核心思路就是手動(dòng)觸發(fā)一次屬性的 getter 來(lái)實(shí)現(xiàn)依賴收集。

總結(jié)

我們?cè)賮?lái)回顧下整個(gè)過(guò)程:

  • 在 Vue 中模板編譯過(guò)程中的指令或者數(shù)據(jù)綁定都會(huì)實(shí)例化一個(gè) Watcher 實(shí)例,實(shí)例化過(guò)程中會(huì)觸發(fā) get() 將自身指向 Dep.target;
  • data在 Observer 時(shí)執(zhí)行 getter 會(huì)觸發(fā) dep.depend() 進(jìn)行依賴收集;依賴收集的結(jié)果:
  1. data在 Observer 時(shí)閉包的dep實(shí)例的subs添加觀察它的 Watcher 實(shí)例;
  2. Watcher 的deps中添加觀察對(duì)象 Observer 時(shí)的閉包dep;
  • 當(dāng)data中被 Observer 的某個(gè)對(duì)象值變化后,觸發(fā)subs中觀察它的watcher執(zhí)行 update() 方法,***實(shí)際上是調(diào)用watcher的回調(diào)函數(shù)cb,進(jìn)而更新視圖。

參考文章和書(shū)籍

作者介紹

浪里行舟:碩士研究生,專(zhuān)注于前端。個(gè)人公眾號(hào):「前端工匠」,致力于打造適合初中級(jí)工程師能夠快速吸收的一系列優(yōu)質(zhì)文章!

【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】

責(zé)任編輯:華軒 來(lái)源: 51CTO
相關(guān)推薦

2022-11-04 09:43:05

Java線程

2024-03-12 00:00:00

Sora技術(shù)數(shù)據(jù)

2022-09-05 08:39:04

kubernetesk8s

2021-03-10 10:55:51

SpringJava代碼

2024-11-01 08:57:07

2020-08-10 18:03:54

Cache存儲(chǔ)器CPU

2024-04-15 00:00:00

技術(shù)Attention架構(gòu)

2023-06-18 12:18:57

2024-05-10 08:18:16

分布式數(shù)據(jù)庫(kù)

2023-09-19 22:47:39

Java內(nèi)存

2022-09-26 08:01:31

線程LIFO操作方式

2020-03-26 16:40:07

MySQL索引數(shù)據(jù)庫(kù)

2022-01-14 12:28:18

架構(gòu)OpenFeign遠(yuǎn)程

2022-09-05 22:22:00

Stream操作對(duì)象

2020-03-17 08:36:22

數(shù)據(jù)庫(kù)存儲(chǔ)Mysql

2020-11-04 15:35:13

Golang內(nèi)存程序員

2023-10-13 13:30:00

MySQL鎖機(jī)制

2023-01-16 18:32:15

架構(gòu)APNacos

2022-08-22 08:04:25

Spring事務(wù)Atomicity

2009-11-16 17:20:04

PHP多維數(shù)組排序
點(diǎn)贊
收藏

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