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

一文搞定數據響應式原理

開發 前端
在Vue中,其中最最最核心的一個知識點就是數據響應式原理,數據響應式原理歸結起來就包含兩大部分:偵測數據變化、依賴收集,了解這兩個知識點就了解到了數據響應式原理的精華。

[[420542]]

在Vue中,其中最最最核心的一個知識點就是數據響應式原理,數據響應式原理歸結起來就包含兩大部分:偵測數據變化、依賴收集,了解這兩個知識點就了解到了數據響應式原理的精華。

一、偵測數據變化

能夠幀聽到數據變化是數據響應式原理的前提,因為數據響應式正是基于監聽到數據變化后來觸發一系列的更新操作。本次介紹數據響應式原理將基于Vue2.x進行,其將數據變為可被偵測數據時主要采用了Object.defineProperty()。

1.1 非數組對象

下面先舉一個非數組對象的例子

  1. const obj = { 
  2.     a: { 
  3.         m: { 
  4.             n: 5 
  5.         } 
  6.     }, 
  7.     b: 10 
  8. }; 

觀察上面的對象,可以發現其是存在包含關系的(即一個對象中可能包含另一個對象),那么自然會想到通過遞歸的方式實現,在Vue中為了保證代碼較高的可讀性,引入了三個模塊實現該邏輯:observe、Observer、defineReactive,其調用關系如下所示:

1.1.1 observe

這個函數是幀聽數據變化的入口文件,通過調用該函數一方面觸發了其幀聽對象數據變化的能力;另一方面定義了何時遞歸到最內層的終止條件。

  1. import Observer from './Observer'
  2.  
  3. export default function (value) { 
  4.     // 如果value不是對象,什么都不做(表示該遞歸到的是基本類型,其變化可被幀聽的) 
  5.     if (typeof value !== 'object') { 
  6.         return
  7.     } 
  8.  
  9.     // Observer實例 
  10.     let ob; 
  11.     // __ob__是value上的屬性,其值就是對應的Observer實例(表示其已經是可幀聽的狀態) 
  12.     if (typeof value.__ob__ !== 'undefined') { 
  13.         ob = value.__ob__; 
  14.     } 
  15.     else { 
  16.         // 是對象且該上屬性還是未能夠幀聽狀態的 
  17.         ob = new Observer(value); 
  18.     } 
  19.  
  20.     return ob; 

1.1.2 Observer

這個函數的目的主要有兩個:一個是將該實例掛載到該對象value的__ob__屬性上(observe上用到了該屬性,通過判斷是否有該屬性判斷是否已經屬于幀聽狀態);另一個是遍歷該對象上的所有屬性,然后將該屬性均變為可幀聽的(通過調用defineReactive實現)。

  1. export default class Observer { 
  2.     constructor(value) { 
  3.         // 給實例添加__ob__屬性 
  4.         def(value, '__ob__', this, false); 
  5.         // 檢查是數組還是對象 
  6.         if (!Array.isArray(value)) { 
  7.             // 若為對象,則進行遍歷,將其上的屬性變為響應式的 
  8.             this.walk(value); 
  9.         } 
  10.     } 
  11.  
  12.     // 對于對象上的屬性進行遍歷,將其變為響應式的 
  13.     walk(value) { 
  14.         for (let key in value) { 
  15.             defineReactive(value, key); 
  16.         } 
  17.     } 

1.1.3 defineReactive

這個方法主要是將Object.defineProperty封裝到一個函數中,做這一步操作的原因是因為Object.defineProperty設置set屬性時需要一個臨時變量來存儲變化前的值,通過封裝利用閉包的思想引入val,這樣就不需要在函數外面再設置臨時變量了。

  1. export default function defineReactive(data, key, val) { 
  2.     if (arguments.length === 2) { 
  3.         val = data[key]; 
  4.     } 
  5.  
  6.     // 子元素要進行observe,至此形成了遞歸 
  7.     let childOb = observe(val); 
  8.  
  9.     Object.defineProperty(data, key, { 
  10.         // 可枚舉 
  11.         enumerable: true
  12.         // 可配置 
  13.         configurable: true
  14.         // getter 
  15.         get() { 
  16.             console.log(`訪問${key}屬性`); 
  17.             return val; 
  18.         }, 
  19.         // setter 
  20.         set(newValue) { 
  21.             console.log(`改變${key}的屬性為${newValue}`); 
  22.             if (val === newValue) { 
  23.                 return
  24.             } 
  25.             val = newValue; 
  26.             // 當設置了新值,這個新值也要被observe 
  27.             childOb = observe(newValue); 
  28.         } 
  29.     }); 

1.2 數組

Object.defineProperty不能直接監聽數組內部的變化,那么數組內容變化應該怎么操作呢?Vue主要采用的是改裝數組方法的方式(push、pop、shift、unshift、splice、sort、reverse),在保留其原有功能的前提下,將其新添加的項變為響應式的。

  1. // array.js文件 
  2. // 得到Array的原型 
  3. const arrayPrototype = Array.prototype; 
  4.  
  5. // 以Array.prototype為原型創建arrayMethods對象,并暴露 
  6. export const arrayMethods = Object.create(arrayPrototype); 
  7.  
  8. // 要被改寫的7個數組方法 
  9. const methodsNeedChange = [ 
  10.     'push'
  11.     'pop'
  12.     'shift'
  13.     'unshift'
  14.     'splice'
  15.     'sort'
  16.     'reverse' 
  17. ]; 
  18.  
  19. methodsNeedChange.forEach(methodName => { 
  20.     //備份原來的方法 
  21.     const original = arrayMethods[methodName]; 
  22.     // 定義新的方法 
  23.     def(arrayMethods, methodName, function () { 
  24.         // 恢復原來的功能 
  25.         const result = original.apply(this, arguments); 
  26.  
  27.         // 將類數組對象轉換為數組 
  28.         const args = [...arguments]; 
  29.         // 數組不會是最外層,所以其上已經添加了Observer實例 
  30.         const ob = this.__ob__; 
  31.  
  32.         // push/unshift/splice會插入新項,需要將插入的新項變成observe的 
  33.         let inserted = []; 
  34.  
  35.         switch (methodName) { 
  36.             case 'push'
  37.             case 'unshift': { 
  38.                 inserted = args; 
  39.                 break; 
  40.             } 
  41.             case 'splice': { 
  42.                 inserted = args.slice(2); 
  43.                 break; 
  44.             } 
  45.         } 
  46.  
  47.         // 對于有插入項的,讓新項變為響應的 
  48.         if (inserted.length) { 
  49.             ob.observeArray(inserted); 
  50.         } 
  51.  
  52.         ob.dep.notify(); 
  53.  
  54.         return result; 
  55.     }, false); 
  56. }); 

除了改裝其原有數組方法外,Observer函數中也將增加對數組的處理邏輯。

  1. export default class Observer { 
  2.     constructor(value) { 
  3.         // 給實例添加__ob__屬性 
  4.         def(value, '__ob__', this, false); 
  5.         // 檢查是數組還是對象 
  6.         if (Array.isArray(value)) { 
  7.             // 改變數組的原型為新改裝的內容 
  8.             Object.setPrototypeOf(value, arrayMethods); 
  9.             // 讓這個數組變為observe 
  10.             this.observeArray(value); 
  11.         } 
  12.         else { 
  13.             // 若為對象,則進行遍歷,將其上的屬性變為響應式的 
  14.             this.walk(value); 
  15.         } 
  16.     } 
  17.  
  18.     // 對于對象上的屬性進行遍歷,將其變為響應式的 
  19.     walk(value) { 
  20.         for (let key in value) { 
  21.             defineReactive(value, key); 
  22.         } 
  23.     } 
  24.  
  25.     // 數組的特殊遍歷 
  26.     observeArray(arr) { 
  27.         for (let i = 0, l = arr.length; i < l; i++) { 
  28.             // 逐項進行observe 
  29.             observe(arr[i]); 
  30.         } 
  31.     } 

二、依賴收集

目前對象中所有的屬性已經變成可幀聽狀態,下一步就進入了依賴收集階段,其整個流程如下所示:

其實看了這張神圖后,由于能力有限還不是很理解,經過自己的拆分,認為可以分成兩個步驟去理解。

1.getter中(Object.defineProperty中的get屬性)進行收集依賴后的狀態

2. 緊接著就是觸發依賴,該過程是在setter中進行,當觸發依賴時所存儲在Dep中的所有Watcher均會被通知并執行,通知其關聯的組件更新,例如數據更新的位置是與Dep1所關聯的數據,則其上的Watcher1、Watcher2、WatcherN均會被通知并執行。

說了這么多,其中最核心的內容無外乎Dep類、Watcher類、defineReactive函數中的set和get函數。

2.1 Dep類

Dep類用于管理依賴,包含依賴的添加、刪除、發送消息,是一個典型的觀察者模式。

  1. export default class Dep { 
  2.     constructor() { 
  3.         console.log('DEP構造器'); 
  4.         // 數組存儲自己的訂閱者,這是Watcher實例 
  5.         this.subs = []; 
  6.     } 
  7.  
  8.     // 添加訂閱 
  9.     addSub(sub) { 
  10.         this.subs.push(sub); 
  11.     } 
  12.  
  13.     // 添加依賴 
  14.     depend() { 
  15.         // Dep.target指定的全局的位置 
  16.         if (Dep.target) { 
  17.             this.addSub(Dep.target); 
  18.         } 
  19.     } 
  20.  
  21.     // 通知更新 
  22.     notify() { 
  23.         const subs = this.subs.slice(); 
  24.         for (let i = 0, l = subs.length; i < l; i++) { 
  25.             subs[i].update(); 
  26.         } 
  27.     } 

2.2 Watcher類

Watcher類的實例就是依賴,在其實例化階段會作為依賴存儲到Dep中,在對應的數據改變時會更新與該數據相關的Watcher實例,進行對應任務的執行,更新對應組件。

  1. export default class Watcher { 
  2.     constructor(target, expression, callback) { 
  3.         console.log('Watcher構造器'); 
  4.         this.target = target; 
  5.         this.getter = parsePath(expression); 
  6.         this.callback = callback; 
  7.         this.value = this.get(); 
  8.     } 
  9.  
  10.     update() { 
  11.         this.run(); 
  12.     } 
  13.  
  14.     get() { 
  15.         // 進入依賴收集階段,讓全局的Dep.target設置為Watcher本身,就進入依賴收集階段 
  16.         Dep.target = this; 
  17.         const obj = this.target; 
  18.         let value; 
  19.  
  20.         try { 
  21.             value = this.getter(obj); 
  22.         } 
  23.         finally { 
  24.             Dep.target = null
  25.         } 
  26.  
  27.         return value; 
  28.     } 
  29.  
  30.     run() { 
  31.         this.getAndInvoke(this.callback); 
  32.     } 
  33.  
  34.     getAndInvoke(cb) { 
  35.         const value = this.get(); 
  36.  
  37.         if (value !== this.value || typeof value === 'object') { 
  38.             const oldValue = this.value; 
  39.             this.value = value; 
  40.             cb.call(this.target, value, oldValue); 
  41.         } 
  42.     } 
  43.  
  44. function parsePath(str) { 
  45.     const segments = str.split('.'); 
  46.  
  47.     return obj =>{ 
  48.         for (let i = 0; i < segments.length; i++) { 
  49.             if (!obj) { 
  50.                 return
  51.             } 
  52.             obj = obj[segments[i]]; 
  53.         } 
  54.  
  55.         return obj; 
  56.     }; 

2.3 defineReactive函數中的set和get函數

Object.defineProperty中的getter階段進行收集依賴,setter階段觸發依賴。

  1. export default function defineReactive(data, key, val) { 
  2.     const dep = new Dep(); 
  3.     if (arguments.length === 2) { 
  4.         val = data[key]; 
  5.     } 
  6.  
  7.     // 子元素要進行observe,至此形成了遞歸 
  8.     let childOb = observe(val); 
  9.  
  10.     Object.defineProperty(data, key, { 
  11.         // 可枚舉 
  12.         enumerable: true
  13.         // 可配置 
  14.         configurable: true
  15.         // getter 
  16.         get() { 
  17.             console.log(`訪問${key}屬性`); 
  18.             // 如果現在處于依賴收集階段 
  19.             if (Dep.target) { 
  20.                 dep.depend(); 
  21.                 // 其子元素存在的時候也要進行依賴收集(個人認為主要是針對數組) 
  22.                 if (childOb) { 
  23.                     childOb.dep.depend(); 
  24.                 } 
  25.             } 
  26.             return val; 
  27.         }, 
  28.         // setter 
  29.         set(newValue) { 
  30.             console.log(`改變${key}的屬性為${newValue}`); 
  31.             if (val === newValue) { 
  32.                 return
  33.             } 
  34.             val = newValue; 
  35.             // 當設置了新值,這個新值也要被observe 
  36.             childOb = observe(newValue); 
  37.             // 發布訂閱模式,通知更新 
  38.             dep.notify(); 
  39.         } 
  40.     }); 

 

責任編輯:姜華 來源: 前端點線面
相關推薦

2024-01-09 08:24:47

JMM核心線程

2022-07-15 08:16:56

Stream函數式編程

2021-10-06 20:23:08

Linux共享內存

2020-05-13 09:14:16

哈希表數據結構

2019-09-23 10:51:14

JavaJava虛擬機Linux

2024-03-26 00:33:59

JVM內存對象

2021-08-13 05:50:01

ContainerdDockerKubernetes

2021-10-25 16:01:01

Linux設備樹字符串

2021-03-28 18:40:02

LinuxWindowsJava

2020-10-27 10:26:03

編程開發Java

2020-10-29 08:55:04

微服務

2022-08-17 18:25:37

Java分布式搜索引擎

2021-08-31 07:02:20

Diff算法DOM

2023-09-08 08:20:46

ThreadLoca多線程工具

2021-04-02 06:17:10

大數加減乘除數據結構算法

2025-04-07 08:20:00

ORMPython代碼

2021-12-16 14:45:09

https架構服務端

2024-07-12 14:46:20

2021-07-08 10:08:03

DvaJS前端Dva

2022-04-15 08:03:41

SaaS應用管理市場
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品亚洲二区 | 米奇成人网 | 欧美电影一区 | 91在线看 | 99爱视频| 国产免费一级片 | 日韩精品久久一区 | 毛片一级电影 | 网站黄色在线 | 国产精品视频网址 | 中国av在线免费观看 | 欧美色性 | 成人欧美一区二区三区在线观看 | 国产十日韩十欧美 | 国产福利在线 | 欧美啪啪网站 | www亚洲精品| 日韩欧美国产一区二区三区 | 91精品国产91久久久久久最新 | 午夜手机在线视频 | 欧美精品一区二区在线观看 | 国产精品一区在线观看 | 91亚洲精品国偷拍自产在线观看 | 一本大道久久a久久精二百 国产成人免费在线 | 久久久久久久久久久久久91 | 一区二区三区在线免费看 | 老司机精品福利视频 | 日韩一区二区在线免费观看 | 欧美一区二区视频 | 在线视频中文字幕 | 欧美激情精品久久久久久变态 | 国产精品亚洲成在人线 | 欧美日韩国产免费 | 精品免费视频一区二区 | 免费看91| 我想看国产一级毛片 | 免费高清av| 日本一区二区三区四区 | 第一福利社区1024 | 国产综合在线视频 | 午夜在线观看视频 |