vue的數組監聽

Vue中對數據的監聽主要依靠Object.defineProperty來實現的,這種實現主要針對key/value形式的對象,對數組中的值的變化是無能爲力的,definrProperty是無法監聽數組長度的變化,監聽索引的代價也很高,那麼應該怎麼對數組中的數據進行監聽呢?

一、數組的變化情況:

  1. 數組本身的賦值
  2. 數組中push等方法導致的變化
  3. 數組中的值變化
  4. 操作數組的長度導致的變化

二、對上面的變化依次分析:

數組本身的賦值

這種情況和對象的監聽是一致的,直接使用defineProperty對數據進行監聽就可以了。

數組中push等方法導致的變化

數組push等操作改變數據時想要監聽數據的變化就沒有辦法通過defineProperty來實現了。那要怎麼實現?

  1. 需要通過Object.create實現一個Array.prototype繼承者arraymethods。它訪問的方法和Array.prototype上的是一樣的。我們不能直接在Array.prototype上對方法進行監聽,因爲這樣會影響到正常方法的調用。
  2. 將push等方法,通過defineproperty直接寫入到arraymethods對象上
const methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'reverse',
    'sort'
];
const arrayProto = Array.prototype, //緩存Array的原型
arrayMethods = Object.create(arrayProto); //繼承Array的原型
//設置對象屬性的工具方法
function def(obj, key, val) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: true,
        writable: true,
        configurable: true
    });
}
methodsToPatch.forEach(function(method, index) {
    def(arrayMethods, method, function(...args) {
        //arrayProto[method].apply(this, args);代表原來的方法
    });
});
  1. 此時,如果直接訪問arraymethods上的七種方法,會直接訪問到arraymethods對象上的,可以在裏面寫一些需要監聽的方法,內部通過arrayProto[method].apply(this,args)訪問到數組上的真正的方法。
  2. 因爲arraymethods是我們構造出來的結構,它本身並不是數組,所以我們要對我們操作的數組通過以下方法讓其指向arraymethods中的方法,而不是真正的Array.prototype中的方法。
if('__proto__' in {}) {
    //瀏覽器中有__proto__,將數組的原型指向arrayMethods,這樣當數組調用上述的7個方法時,其實是調用arrayMethods中的方法而不是調用Array.prototype中的方法
    target.__proto__ = arrayMethods;
} else {
    //如果瀏覽器不支持__proto__,那麼直接將arraymethods中的方法拷貝到數組實例中。則設置數組對應的屬性,這樣當數組調用上述的7個方法時,其實是調用數組對應屬性指向的方法
    for(let i = 0, l = methodsToPatch.length; i < l; i++) {
        let key = methodsToPatch[i];
        def(target, key, arrayMethods[key]);
    }
}

完整代碼:

const patchArray = (function() {
const methodsToPatch = [
    'push',
    'pop',
     'shift',
    'unshift',
    'splice',
    'reverse',
    'sort'
];
//設置對象屬性的工具方法
function def(obj, key, val) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: true,
        writable: true,
        configurable: true
    });
}
const arrayProto = Array.prototype, //緩存Array的原型
arrayMethods = Object.create(arrayProto); //繼承Array的原型
    methodsToPatch.forEach(function(method, index) {
        def(arrayMethods, method, function(...args) {
            //首先調用Array原型的方法
            const old=this.concat([]);
            const res = arrayProto[method].apply(this, args);
            let inserted = null,
            deleted = null;
            let _callback_ = this._callback_;
            //記錄插入的值
            switch(method) {
                case 'push':
                case 'unshift':
                    inserted = args;
                break;
                case 'splice':
                //這是新增的
                    inserted = args.slice(2);
                    let start = args[0],
                    end = start + args[1];
                    deleted = old.slice(start, end);
                break;
                case 'pop':
                case 'shift':
                deleted = res;
            }
            _callback_(inserted, deleted);
            return res;
        });
});
return function(target, callback) {
    def(target, '_callback_', callback); //定義回調
        //看看瀏覽器支不支持__proto__這個屬性,通過改變__proto__的值,可以設置對象的原型
        if('__proto__' in {}) {
            //將數組的原型指向arrayMethods,這樣當數組調用上述的7個方法時,其實是調用arrayMethods中的方法而不是調用Array.prototype中的方法
            target.__proto__ = arrayMethods;
        } else {
            //如果瀏覽器不支持__proto__,則設置數組對應的屬性,這樣當數組調用上述的7個方法時,其實是調用數組對應屬性指向的方法
            for(let i = 0, l = methodsToPatch.length; i < l; i++) {
                let key = methodsToPatch[i];
                def(target, key, arrayMethods[key]);
            }
        }
    }
})();
//測試
let arr = [1, 2, 3];
patchArray(arr, function(add, del) {
if(add)
    console.log('這是新增的內容:', add);
if(del)
    console.log('這是刪除的內容:', del);
});
arr.splice(1,2,'aa','bb','cc')

參考文檔:http://www.qiutianaimeili.com/html/page/2019/05/m0kcbzlpc9s.html
https://www.cnblogs.com/DevinnZ/p/10569033.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章