成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入理解vue響應式原理

原創
開發
Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新。這使得狀態管理非常簡單直接,不過理解其工作原理同樣重要,這樣你可以避開一些常見的問題。----官方文檔 本文將針對響應式原理做一個詳細介紹,并且帶你實現一個基礎版的響應式系統。

【51CTO.com原創稿件】前言

Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的 JavaScript 對象。而當你修改它們時,視圖會進行更新。這使得狀態管理非常簡單直接,不過理解其工作原理同樣重要,這樣你可以避開一些常見的問題。----官方文檔 本文將針對響應式原理做一個詳細介紹,并且帶你實現一個基礎版的響應式系統。本文的代碼請猛戳Github博客

[[269278]]

什么是響應式

我們先來看個例子:

  1. <div id="app"
  2.     <div>Price :¥{{ price }}</div> 
  3.     <div>Total:¥{{ price * quantity }}</div> 
  4.     <div>Taxes: ¥{{ totalPriceWithTax }}</div> 
  5.     <button @click="changePrice">改變價格</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. }) 

上例中當price 發生變化的時候,Vue就知道自己需要做三件事情:

  • 更新頁面上price的值
  • 計算表達式 price*quantity 的值,更新頁面
  • 調用totalPriceWithTax 函數,更新頁面

發生變化后,會重新對頁面渲染,這就是Vue響應式,那么這一切是怎么做到的呢?

想完成這個過程,我們需要:

  • 偵測數據的變化
  • 收集視圖依賴了哪些數據
  • 數據變化時,自動“通知”需要更新的視圖部分,并進行更新

對應專業俗語分別是:

  • 數據劫持 / 數據代理
  • 依賴收集
  • 發布訂閱模式

如何偵測數據的變化

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

方法1.Object.defineProperty實現

Vue通過設定對象屬性的 setter/getter 方法來監聽數據的變化,通過getter進行依賴收集,而每個setter方法就是一個觀察者,在數據變更的時候通知訂閱者更新視圖。

  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. // 判斷類型 
  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) // 監聽 
  22. return value 
  23. }, 
  24. setfunction reactiveSetter (newVal) {  
  25. observe(newVal) //如果賦值是一個對象,也要遞歸子屬性  
  26. if (newVal !== value) { 
  27. console.log('set', newVal) // 監聽 
  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 浪里行舟 

幾個注意點補充說明:

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

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

  • Object.defineProperty 不能監聽數組的變化,需要進行數組方法的重寫
  1. function render() { 
  2. console.log('模擬視圖渲染'
  3. let obj = [1, 2, 3] 
  4. let methods = ['pop''shift''unshift''sort''reverse''splice''push'
  5. // 先獲取到原來的原型上的方法 
  6. let arrayProto = Array.prototype 
  7. // 創建一個自己的原型 并且重寫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] 

這種方法將數組的常用方法進行重寫,進而覆蓋掉原生的數組方法,重寫之后的數組方法需要能夠被攔截。但有些數組操作Vue時攔截不到的,當然也就沒辦法響應,比如:

  1. obj.length-- // 不支持數組的長度變化 
  2.  
  3. obj[0]=1 // 修改數組中***個元素,也無法偵測數組的變化 

ES6提供了元編程的能力,所以有能力攔截,Vue3.0可能會用ES6中Proxy 作為實現數據代理的主要方式。

方法2.Proxy實現

Proxy 是 JavaScript 2015 的一個新特性。Proxy 的代理是針對整個對象的,而不是對象的某個屬性,因此不同于 Object.defineProperty 的必須遍歷對象每個屬性,Proxy 只需要做一層代理就可以監聽同級結構下的所有屬性變化,當然對于深層結構,遞歸還是需要進行的。此外**Proxy支持代理數組的變化。**

  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. // 如果取的值是對象就在對這個對象進行數據劫持  
  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] = '浪里行舟' //支持數組的內容發生變化 
  26. console.log(proxy.arr) // 模擬視圖的更新 ['浪里行舟', 2, 3 ] 
  27. proxy.arr.length-- // 無效 

以上代碼不僅精簡,而且還是實現一套代碼對對象和數組的偵測都適用。不過Proxy兼容性不太好!

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

收集依賴與發布訂閱模式

如何收集依賴,總結起來就一句話,在getter中收集依賴,在setter中觸發依賴 我們先來實現一個 Dep 類,用于解耦屬性的依賴收集和派發更新操作。

  1. // 通過 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. // 全局屬性,通過該屬性配置 Watcher  
  17. Dep.target = null 

當需要依賴收集的時候調用 addSub,當需要派發更新的時候調用 notify。具體如何調用呢?

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

這就是一個簡單實現的“事件發布訂閱模式”,當然代碼只是啟發思路,真實應用還比較“粗糙”,沒有進行事件名設置,APIs 也并不豐富,但完全能夠說明問題了。

接下來我們先來簡單的了解下 Vue 組件掛載時添加響應式的過程。在組件掛載時,會先對所有需要的屬性調用 Object.defineProperty(),然后實例化 Watcher,傳入組件更新的回調。在實例化過程中,會對模板中的屬性進行求值,觸發依賴收集。我們可以把Watcher理解成一個中介的角色,數據發生變化時通知它,然后它再通知其他地方。

***需要對 defineReactive 函數進行改造,在自定義函數中添加依賴收集和派發更新相關的代碼。

  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.   // 判斷類型 
  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) // 監聽 
  24.     // 將 Watcher 添加到訂閱 
  25.        if (Dep.target) { 
  26.          dp.addSub(Dep.target) 
  27.        } 
  28.         return value 
  29.       }, 
  30.       setfunction reactiveSetter (newVal) { 
  31.         observe(newVal) //如果賦值是一個對象,也要遞歸子屬性 
  32.         if (newVal !== value) { 
  33.           console.log('set', newVal) // 監聽 
  34.           render() 
  35.           value = newVal 
  36.      // 執行 watcher 的 update 方法 
  37.           dp.notify() 
  38.         } 
  39.       } 
  40.     }) 
  41.   } 

以上所有代碼實現了一個簡易的數據響應式,核心思路就是手動觸發一次屬性的 getter 來實現依賴收集。

總結

我們再來回顧下整個過程:

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

參考文章和書籍

作者介紹

浪里行舟:碩士研究生,專注于前端。個人公眾號:「前端工匠」,致力于打造適合初中級工程師能夠快速吸收的一系列優質文章!

【51CTO原創稿件,合作站點轉載請注明原文作者和出處為51CTO.com】

責任編輯:華軒 來源: 51CTO
相關推薦

2020-08-10 18:03:54

Cache存儲器CPU

2024-04-15 00:00:00

技術Attention架構

2024-03-12 00:00:00

Sora技術數據

2022-11-04 09:43:05

Java線程

2021-03-10 10:55:51

SpringJava代碼

2022-09-05 08:39:04

kubernetesk8s

2024-11-01 08:57:07

2023-06-18 12:18:57

2020-11-04 15:35:13

Golang內存程序員

2020-03-17 08:36:22

數據庫存儲Mysql

2023-10-13 13:30:00

MySQL鎖機制

2022-09-05 22:22:00

Stream操作對象

2024-05-10 08:18:16

分布式數據庫

2020-03-26 16:40:07

MySQL索引數據庫

2023-09-19 22:47:39

Java內存

2022-09-26 08:01:31

線程LIFO操作方式

2022-01-14 12:28:18

架構OpenFeign遠程

2020-07-03 17:20:07

Redux前端代碼

2009-11-16 17:20:04

PHP多維數組排序

2017-05-04 16:35:45

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久国内精品 | 亚洲国产精品99久久久久久久久 | 亚洲高清视频一区二区 | 精品动漫一区 | 成人av一区 | 久久视频精品 | 亚洲精品电影网在线观看 | 国产二区视频 | 国产精品一区二区三 | 日韩午夜 | 性在线 | 91一区二区在线观看 | 国产婷婷色一区二区三区 | 成人在线免费看 | 亚洲国产欧美91 | 夜夜夜操 | 91精品国产自产在线老师啪 | 一本大道久久a久久精二百 国产成人免费在线 | 成人超碰 | www.久久久| 国产91黄色 | 久久久久久天堂 | 国产精品一区二区久久 | 欧美精品一区二区三区四区五区 | 一级欧美 | 国产黑丝av | 欧美专区在线 | 成人免费在线观看 | av片在线免费看 | 国产一区二区三区久久久久久久久 | 亚洲综合伊人 | 欧美成人a | 999久久久久久久久6666 | 九色一区| 中文字幕一区二区三区四区五区 | 羞羞视频在线观看 | 一a级片 | 国产成人精品一区二区三区网站观看 | 国产精品高潮呻吟久久 | 亚洲精品福利视频 | 欧美成人一区二区 |