Vueにおける変異配列


余力のある相手
Vueでは、オブジェクトのプロパティの値を直接変更すると、応答式はトリガーされません.つまり、データを変更してもページの内容は変更されません.
これは何の原因ですか.
なぜなら、Vueの応答式システムはObjectに基づいているからである.defineProperty()この方法は、オブジェクト内の要素の取得または変更を傍受することができ、この方法で処理されたデータを応答型データと呼ぶ.しかし、この方法には、新しいプロパティを追加したり削除したりしても、リスニングがトリガーされないという大きな欠点があります.
次のコードは例です.
var vm = new Vue({
    data () {
        return {
            obj: {
                a: 1
            }
        }
    }
})

//  vm.obj.a         

vm.obj.b = 2

//  vm.obj.b        

このような理由は、Vueが初期化する際に、その内部でdataメソッドを深い応答式処理して応答式データにするためvmであるにほかならない.obj.aは応答式である.
一方、vm.obj.b Vue初期化時の応答式の洗礼はなく,応答式ではないのは当然である.
では、vm.obj.b応答式になってもいいですか? 
答えは肯定的でvm.$setメソッドは要求を完璧に実現できるが,このメソッドは本稿の重点ではなく,しばらく表を押さない.
もっと惨めな配列
前述の多くの記述はVueにおける応答式を少し説明したが,次に配列について具体的な解析を行う.
オブジェクトよりも配列の境遇が惨めで、公式文書を見てみましょう.
JavaScriptの制限により、Vueは以下の変動する配列を検出できません.
  • インデックスを使用して直接アイテムを設定する場合、例えばvm.items[indexOfItem] = newValue
  • 配列の長さを変更すると、例えばvm.items.length = newLength

  • 次のコードを説明します.
    var vm = new Vue({
        data () {
            return {
                items: ['a', 'b', 'c']
            }
        }
    })
    
    vm.items[1] = 'x' //       
    
    vm.items.length = 2 //       

    配列は、自身の要素の変更も傍受できないが、Vueがdataメソッドで返されるオブジェクトの要素に応答して処理すると同時に、要素が配列であれば、配列自体に応答化するだけで、配列内部の要素は共命ではないことがわかる.
    これにより、配列の内部要素を直接変更して応答式をトリガーすることはできません.
    では、解読方法はありますか?
    もちろん、公式には7つの配列方法が規定されていますが、この7つの配列方法によって、配列の応答式を楽しくトリガすることができます.この7つの配列方法はそれぞれ:
  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

  • この7つの配列方法は原生の配列方法のように見えますが、なぜこの7つの配列方法は応式をトリガし、ビューの更新をトリガすることができますか?
    あなたは心の中で考えています:配列の方法は起きませんか、配列の方法は好きなようにすることができますか?
    騒瑞よ、この7つの配列方法は本当にやりたい放題です.
    変異後の配列法だからです
    配列変異の考え方
    変異配列法とは?
    変異配列法:配列法の元の機能を一定に保つ前提の下で、それに対して機能の開拓を行う.Vueでは,この機能拡張とは応答式機能を追加することである.
    通常の配列を変異配列に変更する方法は、次の2つのステップに分けられます.
  • 機能拡張
  • 配列ハイジャック
  • 機能拡張
    まず考えてみましょう.
    既存の関数の機能や呼び出し方法を変更せずに、その関数を呼び出すたびにコンソールに「HelloWorld」を印刷できるようにする必要がある.
    実は考え方は簡単で、3つのステップに分かれています.
  • 新しい変数キャッシュ元関数
  • を使用
  • 元の関数
  • を再定義
  • 新しい定義の関数で元の関数
  • を呼び出す.
    次のコードは、その具体的な実装です.
    function A () {
        console.log('     A')
    }
    
    const nativeA = A
    
    A = function () {
        console.log('HelloWorld')
        nativeA()
    }

    この方式により,A関数の挙動を変えずに機能拡張を行うことを保証したことが分かる.
    次に、この方法を使用して、配列の元の方法を機能的に拡張します.
    //          
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    const arrayProto = Array.prototype
    
    //          
    const arrayMethods = Object.create(arrayProto)
    
    mutationMethods.forEach(method => {
        //         
        const original = arrayProto[method]
    
        arrayMethods[method] = function (...args) {
            const result = original.apply(this, args)
    
            console.log('       ')
    
            return result
        }
    })

    コードからarrayMethodsというオブジェクトを呼び出す方法には2つのケースがあります.
  • 呼び出し機能拡張方法:arrayMethodsを直接呼び出す方法
  • 原生メソッドを呼び出す:この場合、配列原型に定義された原生メソッド
  • をプロトタイプチェーンで検索する.
    上記の方法により,配列原生法の機能拡張を実現したが,配列インスタンスに機能拡張後配列法を呼び出すにはどうすればよいかという大きな問題が目の前に置かれている.
    この問題を解決する方法は、配列ハイジャックです.
    アレイハイジャック
    配列ハイジャック:名前の通り、元の配列インスタンスを継承する方法を、私たちの機能拡張後の方法に置き換えます.
    考えてみれば、前に機能拡張後の配列arrayMethodsを実現しました.このカスタム配列は、配列オブジェクトから継承されています.通常の配列インスタンスと接続して、通常の配列を継承するだけでいいです.
    上記の操作を実現するには,プロトタイプチェーンを通過する.
    実装方法は次のコードで示されています.
    let arr = []
    
    //         arrayMethods
    arr.__proto__ = arrayMethods
    
    
    //        
    arr.push(1)

    機能拡張と配列ハイジャックにより,ついに変異配列を実現し,次にVueソースコードがどのように変異配列を実現したかを見てみよう.
    ソース解析
    src/core/observer/indexに来ました.jsのObserverクラスのconstructor関数:
    constructor (value: any) {
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
    
        //        
        if (Array.isArray(value)) {
            //     
            const augment = hasProto
            ? protoAugment
            : copyAugment
    
            //                      
            augment(value, arrayMethods, arrayKeys)
    
            //          
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }

    ObserverというクラスはVue応答式システムの核心構成部分であり,初期化段階で最も主要な機能はターゲットオブジェクトを応答式化することである.
    ここでは,配列の処理に主に注目する.
    配列の処理は主に次のコードです.
    //     
    
    const augment = hasProto
    
    ? protoAugment
    
    : copyAugment
    
    //                      
    
    augment(value, arrayMethods, arrayKeys)
    
    //          ,       ,  
    
    this.observeArray(value)
    
    

    まずaugment定数を定義し,この定数の値はhasProtoによって決定される.
    hasProtoを見てみましょう
    export const hasProto = '__proto__' in {}

    hasProtoは、ブラウザが直接使用をサポートしているかどうかを示すブール値定数であることがわかります.proto__ (暗黙のプロトタイプ).
    したがって、第1セグメントのコードはよく理解されています.能力検出結果に基づいて異なる配列ハイジャック方法を選択します.
    ブラウザが暗黙的なプロトタイプをサポートしている場合、protoAugment関数を配列ハイジャックの方法として呼び出し、逆にcopyAugmentを使用します.
    異なる配列ハイジャック方法
    protoAugmentとcopyAugmentを見てみましょう.
    function protoAugment (target, src: Object, keys: any) {
    
      /* eslint-disable no-proto */
    
      target.__proto__ = src
    
      /* eslint-enable no-proto */
    
    }

    protoAugment関数は極めて簡潔であり,配列変異の構想で述べた方法と一致している:配列インスタンスを直接暗黙的プロトタイプを介して変異配列に接続し,この方法によって変異配列中の方法を継承する.
    次にcopyAugmentを見てみましょう.
    function copyAugment (target: Object, src: Object, keys: Array) {
    
      for (let i = 0, l = keys.length; i < l; i++) {
    
        const key = keys[i]
    
        // Object.defineProperty   
    
        def(target, key, src[key])
    
      }
    
    }

    この場合、ブラウザは暗黙的なプロトタイプを直接使用することをサポートしないため、配列ハイジャック方法は面倒です.この関数が受信する最初のパラメータは配列インスタンスであり,2番目のパラメータは変異配列であることを知っているが,3番目のパラメータは何であるか. 
    //                  
    
    const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

    arrayKeysは、このファイルの先頭に、変異配列内のすべての自己属性の属性名を定義し、配列です.
    copyAugment関数を振り返ると,すべての変異配列における方法を配列インスタンス自体に直接定義し,変相に相当して配列のハイジャックを実現したことが明らかになった.
    配列ハイジャックを実現した後,Vueで配列の機能拡張をどのように実現しているかを見てみよう.
    機能拡張
    配列機能拡張のコードはsrc/core/observer/arrayにある.js、コードは以下の通りです.
    import { def } from '../util/index'
    
    //       
    const arrayProto = Array.prototype
    
    //    arrayMethods.__proto__ === Array.prototype
    export const arrayMethods = Object.create(arrayProto)
    
    //            
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    
    /**
    
     * Intercept mutating methods and emit events
    
     */
    
    methodsToPatch.forEach(function (method) {
    
      // cache original method
      //         
      const original = arrayProto[method]
    
      //               
      def(arrayMethods, method, function mutator (...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 change
        ob.dep.notify()
    
        //              
        return result
    
      })
    
    })

    ソースコードは実装方式において,本論文で配列変異構想で用いた方法と一致し,応答式の処理が追加されたにすぎないことが分かった.
    まとめ
    Vueの変異配列は本質的に装飾器モードであり,その原理を学ぶことで,従来の機能を一定に保つ前提で機能拡張のニーズを実際の作業で容易に処理できる.