VUE - MVVM - part6 - Array

看這篇之前,假如沒有看過之前的文章,可拉到文章末端檢察之前的文章。

回憶

在前面的幾個 step 中,我們完成對象的屬性的監聽,然則有關於數組的行動我們一向沒有處置懲罰。
我們先剖析下致使數組有哪些行動:

  1. 挪用要領:arr.splice(1, 2, 'something1', 'someting2')
  2. 直接賦值:arr[1] = 'something'

處理行動一

起首我們曉得數組下的一些要領是會對原數組照成影響的,有以下幾個:

  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse

這幾個要領總的來說會照成幾個影響:

  • 數組長度發生變化
  • 數組內元素遞次發生變化

不像對象,假如對象的 key 值的遞次發生變化,是不會影響視圖的變化,但數組的遞次假如發生變化,視圖是要變化的。

也就是說當著幾個要領觸發的時刻,我們須要視圖的更新,也就是要觸發 Dep 中的 notify 函數。

然則縱觀我們如今完成的代碼( step5 中的代碼),我們並沒有專程的為數組供應一個 Dep

而且上述的幾個數組要領是數組對象供應的,我們要想方法去觸發 Dep 下的 notify 函數。

我們先為數組供應一個 Dep ,完善後的 Observer

export class Observer {

    constructor(value) {
        this.value = value
        if (Array.isArray(value)) {
            // 為數組設置一個特別的 Dep
            this.dep = new Dep()
            this.observeArray(value)
        } else {
            this.walk(value)
        }
        Object.defineProperty(value, '__ob__', {
            value: this,
            enumerable: false,
            writable: true,
            configurable: true
        })
    }

    /**
     * 遍歷對象下屬性,使得屬性變成可監聽的構造
     */
    walk(obj) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]])
        }
    }

    /**
     * 同上,遍曆數組
     */
    observeArray (items) {
        for (let i = 0, l = items.length; i < l; i++) {
            observe(items[i])
        }
    }

}

一樣的在 defineReactive 我們須要處置懲罰數組增加依靠的邏輯

export function defineReactive(object, key, value) {
    let dep = new Dep()
    let childOb = observe(value)
    Object.defineProperty(object, key, {
        configurable: true,
        enumerable: true,
        get: function () {
            if (Dep.target) {
                dep.addSub(Dep.target)
                Dep.target.addDep(dep)
                // 處置懲罰數組的依靠
                if(Array.isArray(value)){
                    childOb.dep.addSub(Dep.target)
                    Dep.target.addDep(childOb.dep)
                }
            }
            return value
        },
        set: function (newValue) {
            if (newValue !== value) {
                value = newValue
                dep.notify()
            }
        }
    })
}

ok 我們如今完成了依靠的增加,剩下的我們要完成依靠的觸發。

處置懲罰要領:在數組對象挪用特定要領時,起首找到的應該是我們本身寫的要領,而這個要領中挪用了原始要領,並觸發依靠。

我們先來包裝一下要領,獲得一些同名要領:

const arrayProto = Array.prototype
// 複製要領
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]

/**
 * 轉變數組的默許處置懲罰,將新增加的對象增加監聽
 */
methodsToPatch.forEach(function (method) {
    // 原始的數組處置懲罰要領
    const original = arrayProto[method]
    let mutator = function (...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break
            case 'splice':
                inserted = args.slice(2)
                break
        }
        // 新增加的對象須要增加監聽
        if (inserted) ob.observeArray(inserted)
        // 觸發 notify 要領
        ob.dep.notify()
        return result
    }
    Object.defineProperty(arrayMethods, method, {
        value: mutator,
        enumerable: false,
        writable: true,
        configurable: true
    })
})

ok 我們如今獲得了一些列同名的要領,我只需確保在挪用時,先挪用到我們的要領即可。

有兩種體式格局能夠完成:

  1. 數組對象上直接有該要領,如許就不會去找對象上的原型鏈
  2. 掩蓋對象的 __proto__ ,如許尋覓原型鏈時,就會先找到我們的要領

詳細到代碼中的完成:

export class Observer {

    constructor(value) {
        this.value = value
        if (Array.isArray(value)) {
            this.dep = new Dep()
            const augment = ('__proto__' in {})
                ? protoAugment
                : copyAugment
            // 掩蓋數組中一些轉變了原數組的要領,使得要領得以監聽
            augment(value, arrayMethods, arrayKeys)
            this.observeArray(value)
        } else {
            this.walk(value)
        }
        ...
    }
    ...
}

/**
 * 假如能運用 __proto__ 則將數組的處置懲罰要領舉行替代
 */
function protoAugment (target, src, keys) {
    target.__proto__ = src
}

/**
 * 假如不能運用 __proto__ 則直接將該要領定義在當前對象下
 */
function copyAugment (target, src, keys) {
    for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i]
        Object.defineProperty(target, key, {
            value: src[key],
            enumerable: false,
            writable: true,
            configurable: true
        })
    }
}

測試一下:

let object = {
    arrayTest: [1, 2, 3, 4, 5]
}

observe(object)

let watcher = new Watcher(object, function () {
    return this.arrayTest.reduce((sum, num) => sum + num)
}, function (newValue, oldValue) {
    console.log(`監聽函數,數組內一切元素 = ${newValue}`)
})

object.arrayTest.push(10)
// 監聽函數,數組內一切元素 = 25

到如今為止,我們勝利的在數組挪用要領的時刻,增加並觸發了依靠。

處理行動二

起首先申明,數組下的索引是和對象下的鍵有一樣的表現,也就是能夠用 defineReactive 來處置懲罰索引值,然則數組是用來寄存一系列的值,我們並不能一開始就肯定數組的長度,而且極有能夠剛開始數組長度為 0,以後數組中的索引對應的內容也會不停的變化,所認為索引挪用 defineReactive 是不切實際的。

然則類似於 arr[1] = 'something' 如許的賦值在數組中也是罕見的操縱,在 Vue 中完成了 $set 詳細的細節這裏不談,這裏完成了另一種要領,我們僅僅須要在數組對象下增加一個要領即可:

arrayMethods.$apply = function () {
    this.__ob__.observeArray(this)
    this.__ob__.dep.notify()
}

測試一下:

object.arrayTest[1] = 10
object.arrayTest.$apply()
// 監聽函數,數組內一切元素 = 33

到現在為了,一個完全的數據監聽的模子也就完成了,我們能夠運用 observe 要領來獲得一個可監聽構造,然後用 Watcher 增加依靠。

在設置值的時刻就可以勝利觸發依靠。

點擊檢察相干代碼

系列文章地點

  1. VUE – MVVM – part1 – defineProperty
  2. VUE – MVVM – part2 – Dep
  3. VUE – MVVM – part3 – Watcher
  4. VUE – MVVM – part4 – 優化Watcher
  5. VUE – MVVM – part5 – Observe
  6. VUE – MVVM – part6 – Array
  7. VUE – MVVM – part7 – Event
  8. VUE – MVVM – part8 – 優化Event
  9. VUE – MVVM – part9 – Vue
  10. VUE – MVVM – part10 – Computed
  11. VUE – MVVM – part11 – Extend
  12. VUE – MVVM – part12 – props
  13. VUE – MVVM – part13 – inject & 總結
    原文作者:aco
    原文地址: https://segmentfault.com/a/1190000014499034
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞