VUE - MVVM - part6 - Array

6925 ワード

この文章を読む前に、前の文章を見たことがなければ、文章の末尾に引っ張って前の文章を見ることができます.
レビュー
前のいくつかのstepでは,オブジェクトの属性の傍受を実現したが,配列に関する挙動はずっと処理されていない.まず、配列にどのような動作をもたらすかを分析します.
  • 呼び出し方法:arr.splice(1, 2, 'something1', 'someting2')
  • 直接付与:arr[1] = 'something'
  • 解決行為一
    まず、配列の下のいくつかの方法が元の配列に影響を与えることを知っています.以下のいくつかがあります.
  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse

  • これらの方法は総じていくつかの影響を及ぼします.
  • 配列長が変化する
  • 配列内の要素の順序が変化する
  • オブジェクトとは異なり、オブジェクトのkey値の順序が変化した場合、ビューの変化には影響しませんが、配列の順序が変化した場合、ビューは変化します.
    すなわち、いくつかの方法がトリガーされると、Depnotify関数をトリガーするビューの更新が必要になります.
    しかし、現在実装されているコード(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私たちは今いくつかの列の同名の方法を得ました.私は呼び出し時に、まず私たちの方法を呼び出すことを確保すればいいです.
    次の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

    これまで,配列呼び出しメソッドの際に依存を追加しトリガすることに成功した.
    解決行為2
    まず、配列の下のインデックスは、オブジェクトの下のキーと同様の表現であり、つまりdefineReactiveでインデックス値を処理することができるが、配列は一連の値を格納するために用いられ、最初から配列の長さを決定することはできず、最初から配列の長さが0である可能性が高く、その後も配列中のインデックスに対応する内容が変化し続ける可能性が高いことを説明する.したがって、インデックスに対してdefineReactiveを呼び出すのは現実的ではない.
    しかし、arr[1] = 'something'のような付与値は配列においても一般的な動作であり、Vueにおいて$setの具体的な詳細が実現されている.ここでは、配列オブジェクトの下に1つの方法を追加するだけでよい別の方法が実現されている.
    arrayMethods.$apply = function () {
        this.__ob__.observeArray(this)
        this.__ob__.dep.notify()
    }

    テスト:
    object.arrayTest[1] = 10
    object.arrayTest.$apply()
    //     ,        = 33

    現在では、完全なデータ傍受モデルも完了し、observe法を用いて傍受可能な構造を得ることができ、その後、Watcherで依存性を追加することができる.
    値を設定すると依存が正常にトリガーされます.
    クリックして関連コードを表示
    シリーズ記事アドレス
  • VUE - MVVM - part1 - defineProperty
  • VUE - MVVM - part2 - Dep
  • VUE - MVVM - part3 - Watcher
  • VUE-MVVM-part 4-最適化Watcher
  • VUE - MVVM - part5 - Observe
  • VUE - MVVM - part6 - Array
  • VUE - MVVM - part7 - Event
  • VUE-MVVM-part 8-最適化Event
  • VUE - MVVM - part9 - Vue
  • VUE - MVVM - part10 - Computed
  • VUE - MVVM - part11 - Extend
  • VUE - MVVM - part12 - props
  • VUE-MVVM-part 13-inject&総括