vue2 和 vue3 的數據綁定原理
作者:yingmhd
VUE 雙向綁定原理
首先Vue
是個類
1new Vue({
2 data(){},
3 methods: {}
4})
5
實例化Vue
的時候要傳入data
,Vue
類內部對data
進行劫持轉換成getter/setter
,如何劫持
vue2 數據劫持
核心方法: Object.defineProperty
H5 方法,所以不兼容 IE8 以下
1let obj = {},
2value = 1
3Object.defineProperty(obj,'a',{
4 get() {
5 console.log('這裏監聽到了數據獲取')
6 return value
7 },
8 set(newValue, value) {
9 if(newValue !== value) {
10 value = newValue
11 console.log('這裏監聽到了數據更改')
12 }
13 }
14})
15console.log(obj.a) // 這裏監聽到了數據獲取 1
16obj.a = 2 // 這裏監聽到了數據更改
17
所以再初始化Vue
時,對data
進行了劫持,每個屬性都通過Object.defineProperty
變成了getter/setter
,一旦數據發生改變,就會觸發set
,然後去更新view
1let data = {
2 name: 'nike',
3 info: {
4 age: 21
5 }
6}
7Object.keys(data).forEach(key=>{
8 defineProperty(data, key, data[key])
9})
10function defineProperty(target, key, value) {
11 Object.defineProperty(target,key,{
12 get() {
13 console.log('這裏監聽到了數據獲取')
14 return value
15 },
16 set(newValue, value) {
17 if(newValue !== value) {
18 value = newValue
19 console.log('這裏監聽到了數據更改')
20 }
21 }
22 })
23}
24data.name = 'tom' // 這裏監聽到了數據更改
25data.info.age = 22 // 這裏監聽到了數據獲取(這裏沒有觸發更改,get和set相對立,總要觸發一個)
26data.info = {age:22} // 這裏監聽到了數據更改
27
至於data.info.age = 22
爲什麼沒有觸發set
呢,因爲上面的邏輯僅僅是對data
下面的一層進行了劫持
,而再往下的改變是監聽不到的,所以就引出了兩外一個東西
-
Watch
1watch: { 2 info: { 3 handler(){}, 4 deep: true 5 } 6} 7
此處的
deep
表示深度監聽,這樣就會對該屬性遞歸遍歷並逐一劫持,類似於深拷貝 -
vue.$set
從字面意思看,就是手動觸發set
Object.defineProperty
有一個 bug,就是無法監聽數組(因爲數組沒key
)
1let data = {
2 name: [],
3}
4Object.keys(data).forEach(key=>{
5 defineProperty(data, key, data[key])
6})
7function defineProperty(target, key, value) {
8 Object.defineProperty(target,key,{
9 get() {
10 console.log('這裏監聽到了數據獲取')
11 return value
12 },
13 set(newValue, value) {
14 if(newValue !== value) {
15 value = newValue
16 console.log('這裏監聽到了數據更改')
17 }
18 }
19 })
20}
21data.name.push('nike') // 這裏監聽到了數據獲取
22
爲了解決這個問題,Vue
對數組的方法進行了重寫
1// 重寫push
2let oldPush = Array.prototype.push
3Array.prototype.push = function() {
4 console.log('這裏觸發view更新')
5 oldPush.call(this,...arguments)
6}
7
vue3 數據劫持
很明顯,Object.defineProperty
有一些缺陷,不僅要遍歷data
逐個劫持,還不能監聽到數組的改變,所以VUE3
使用了ES6
的Proxy
Proxy
字面理解代理
, 就跟經紀人一樣,一旦與某個明星data
綁定,那麼這個明星想幹嘛就得先通過代理
1let data = {
2 msg: {
3 a: 10
4 },
5 arr: [1, 2, 3]
6}
7let handler = {
8 get(target, key) {
9 // 懶監聽,去獲取的時候才監聽對象裏面的對象,而不是直接遞歸循環監聽
10 console.log('獲取key: ' + key)
11 if (typeof target[key] === 'object' && target[key] !== null) {
12 return new Proxy(target[key], handler)
13 }
14 return Reflect.get(target, key)
15 },
16 set(target, key, value) {
17 let oldValue = target[key]
18 console.log('更新key: ' + key)
19 if (oldValue !== value) {
20 // 通知view更新
21 }
22 return Reflect.set(target, key, value)
23 }
24}
25let proxy = new Proxy(data, handler)
26proxy.arr.push(4)
27
輸出結果
爲什麼每次都有length
,其實Proxy
的監聽數組實現是把數組變成了一個類數組對象
而已
1let arr = {
2 '0': 1,
3 '1': 2,
4 length: 2
5}
6
Proxy
除了get
,set
還有deleteProperty/apply/getOwnPropertyDescriptor
等等 12 個方法,恰好與Reflect
對應,所以在這些方法裏面可以實現攔截器
1set(target, key, value) {
2 if(key[0] === '_') {
3 throw new Error('這是私有變量,不能更改')
4 }
5 return Reflect.set(target, key, value)
6}
7
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/KKDG0R2Ps6MGC0eIFiOIPw