Vueソース学習の2:配列の変化を傍受する

5014 ワード

前回のコードでは,配列の処理を無視し,関心が必要な部分だけに関心を持ち,配列が存在しないふりをした.
この編では配列の問題を考え始めた.
最も簡単なことから
まず、配列内のオブジェクトの変化をどのように傍受するかという問題を考えます.配列自体とその一般値を無視し、オブジェクト配列内のオブジェクトのみを考慮します.
配列を巡回し、配列内の各オブジェクトに対してobserveメソッドを呼び出します.
//               ,        
var Observer = function Observer(value) {
    this.value = value;
    this.dep = new Dep();
    //      ,       
    if(Array.isArray(value)) {
        this.observeArray(value);
    } else {
        this.walk(value);
    }
};
Observer.prototype.observeArray = function observeArray(items) {
    //         ,        getter、setter   
    for (var i = 0, l = items.length; i < l; i++) {
        observe(items[i]);
    }
};

現実的な要求.
実際の実装では、上記の例のように簡単ではありません.公式ドキュメントでは、リスニング配列について説明しています.
Vueには、配列を観察する突然変異のセットが含まれているため、ビューの更新もトリガーされます.これらの方法は、push()、pop()、shift()、unshift()、splice()、sort()、reverse()
JavaScriptの制限のため、Vueは以下の変動の配列を検出できません:あなたが直接1つの項目のインデックスを設定する時、例えば:vm.items[indexOfItem]=newValue配列の長さを変更すると、たとえばvm.items.length = newLength
したがって,配列自体のいくつかの方法を傍受する.
よく使われる小さな関数
defは、Vueソースコード全体にわたって繰り返され、Objectを利用する.defineProperty()objで属性keyを定義します(既存の属性keyを変更することもできます):
function def(obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
        value: val,
        //     boole  ,     ,   false
        enumerable: !!enumerable,
        writable: true,
        configurable: true
    });
}

オブジェクトにメソッドのセットを追加
オブジェクトにメソッドのセットを追加し、環境でprotoをサポートすれば簡単です.オブジェクトのprotoを直接このメソッドのセットに指さすといいです.サポートされていない場合は、このメソッドのセットを巡り、非表示属性(enumerable:false、inキーでは見つかりません):
var hasProto = '__proto__' in {};
var augment = hasProto ? protoAugment : copyAugment;

function protoAugment(target, src) {
    target.__proto__ = src;
}
function copyAugment(target, src, keys) {
    for(var i = 0; i < keys.length; i++) {
        var key = keys[i];
        def(target, key, src[key]);
    }
}

まず簡単なものをください.
var arrayPush = {};

(function(method){
    var original = Array.prototype[method];
    arrayPush[method] = function() {
        // this             
        console.log(this);
        return original.apply(this, arguments)
    };
})('push');
var testPush = [];
testPush.__proto__ = arrayPush;
//     ,         this      testPush
// []
testPush.push(1);
// [1]
testPush.push(2);

配列の変化を監視するために配列プロトタイプを擬似書き換え
公式文書で述べたように、監視が必要なのはpush()、pop()、shift()、unshift()、splice()、sort()、reverse()の7つの方法だけで、この7つの方法には2つの種類があります.
  • push()、unshift()、splice()の3つの配列に新しい要素を追加する可能性のある方法.
  • 残りの要素を追加しない方法.

  • 全体を汚染するArrayを避けるために、Array.prototypeはプロトタイプのオブジェクトであり,その後そのオブジェクト自体に属性を付加し,この新規のオブジェクトをプロトタイプとしてあるいはObserverのvalueに属性として追加し,その変化を監視する目的を達成する.
    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);
    

    次に、arrayMethodsに更新をトリガーするいくつかの方法を巡回します.
    ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function(method) {
        //            
        var original = arrayProto[method];
        //   arrayMethods       method,   method    (  )
        //     arrayMethods         
        def(arrayMethods, method, function mutator() {
            var arguments$1 = arguments;
            
            var i = arguments.length;
            var args = new Array(i);
            //      arguments        
            //      [].slice.call(arguments)?
            while(i--) {
                args[i] = arguments$1[i];
            }
            var result = original.apply(this, args);
            //   arrayMethods       Observer    value            ,      this        Observer    value
            //   ,      Observer,      value       Observer      ,__ob__,         
            var ob = this.__ob__;
            //          
            var inserted;
            //                  
            switch(method) {
                case 'push':
                    inserted = args;
                    break;
                case 'unshift':
                    inserted = args;
                    break;
                case 'splice':
                    // splice                 
                    inserted =args.slice(2);
                    break;
            }
            if(inserted) {
                //         getter、setter   
                ob.observerArray(inserted);
            }
            //     
            ob.dep.notify();
            return result;
        });
    };
    
    var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
    

    Observerの更新
    上記の例のコードの注釈に従って、Observerを書き換え、両者を関連付けて配列の変化を傍受する目的を達成する.
    var Observer = function Observer(value) {
        this.value = value;
        this.dep = new Dep();
        def(value, '__ob__', this);
        //      ,       
        if(Array.isArray(value)) {
            var argument = hasProto ? protoAugment : copyAugment;
            argument(value, arrayMethods, arrayKeys);
            this.observeArray(value);
        } else {
            this.walk(value);
        }
    };
    

    参考資料:vue初期ソース学習シリーズの2:配列の変化をどのように傍受するか