vue2 和 vue3 的數據綁定原理

作者:yingmhd

VUE 雙向綁定原理

首先Vue是個類

1new Vue({
2    data(){},
3    methods: {}
4})
5

實例化Vue的時候要傳入dataVue類內部對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下面的一層進行了劫持,而再往下的改變是監聽不到的,所以就引出了兩外一個東西

  1. Watch

    1watch: {
    2    info: {
    3        handler(){},
    4        deep: true
    5    }
    6}
    7    
    

    此處的deep表示深度監聽,這樣就會對該屬性遞歸遍歷並逐一劫持,類似於深拷貝

  2. 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使用了ES6Proxy
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