文章出處

實現監聽數組方法

var ArrayProxy = Object.create(Array.prototype),
        methods  = ['push','pop','shift','unshift','splice','sort','reverse'];
    function defProtected(obj, key, val, enumerable, configurable) {
        // 如果是用戶添加的方法則不監聽
        if (obj.hasOwnProperty(key)) return
        Object.defineProperty(obj, key, {
            value        : val,
            // 不可枚舉
            enumerable   : !!enumerable,
            // 不可配置
            configurable : !!configurable
        })
    }
        // 監聽原生數組方法
    methods.forEach(function (method) {
        // ArrayProxy監聽的對象 ,method監聽的方法,第三個返回一個value
        defProtected(ArrayProxy, method, function () {
            // 這里面的this表示當前調用的數組
            var result = Array.prototype[method].apply(this, arguments)
            // 調用數組的__observer__里面的emit方法,觸發更新。
            this.__observer__.emit('mutate', this.__observer__.path, this, {
                method: method,
                args: slice.call(arguments),
                result: result
            })
            return result
        }, !hasProto)
    });

我們可以看到在這段代碼中并沒有對數組進行get和set監聽,這也是為什么在vue中給數組直接賦值不會觸發更新的主要原因。

數組remove和replace方法

var hasProto = ({}).__proto__;

function def(obj, key, val, enumerable, configurable) {
    if (obj.hasOwnProperty(key)) return
    Object.defineProperty(obj, key, {
        value        : val,
        enumerable   : !!enumerable,
        configurable : !!configurable
    })
}

var ArrayProxy = Object.create(Array.prototype);

// 給數組添加remove和replace方法
var extensions = {
    remove: function (index) {
        /*
            如果index是一個函數,則調用這個函數并且判斷返回值,如果返回值為true則刪除,false不刪除
            
            比如下面這個,刪除index大于5的項
            remove(function(index){
                return index > 5;
            });

         */
        if (typeof index === 'function') {
            var i = this.length,
                removed = []
            while (i--) {
                if (index(this[i])) {
                    removed.push(this.splice(i, 1)[0])
                }
            }
            // 將刪除的項返回,返回后為新數組
            return removed.reverse()
        } else {
            // 這個判斷是為了實現如果數組項是字符串也能刪除
            if (typeof index !== 'number') {
                index = this.indexOf(index)
            }
            if (index > -1) {
                return this.splice(index, 1)[0]
            }
        }
    },
    replace: function (index, data) {
        if (typeof index === 'function') {
            var i = this.length,
                replaced = [],
                replacer
            while (i--) {
                replacer = index(this[i])
                /* 
    
        這里之所以不是直接判斷if(replacer)是因為這里的目的就是實現替換功能,而如果值不是undefined說明用戶有返回值而只要有返回值就應該給它替換。


                */
                if (replacer !== undefined) {
                    replaced.push(this.splice(i, 1, replacer)[0])
                }
            }
            return replaced.reverse()
        } else {
            if (typeof index !== 'number') {
                index = this.indexOf(index)
            }
            if (index > -1) {
                return this.splice(index, 1, data)[0]
            }
        }
    }
}

for (var method in extensions) {
    // 給ArrayProxy原型添加remove,replace方法,并且監聽
    def(ArrayProxy, method, extensions[method], !hasProto)
}

實現監聽對象方法

/**
 *  根據類型觀察對象,入口
 */
function watch (obj, path, observer) {
    var type = typeOf(obj)
    if (type === 'Object') {
        watchObject(obj, path, observer)
    } else if (type === 'Array') {
        watchArray(obj, path, observer)
    }
}

/**
 *  監聽對象變化,但不監聽對象中開頭為$和_的屬性和方法,入口
 */
function watchObject (obj, path, observer) {
    for (var key in obj) {
        var keyPrefix = key.charAt(0)
        if (keyPrefix !== '$' && keyPrefix !== '_') {
            bind(obj, key, path, observer)
        }
    }
}

/**
 *  監聽數組方法,并將其原型掛載到ArrayProxy上,入口
 *  ArrayProxy方法實現了一些變異的數組方法以及擴展,這是實現對數組方法監聽的基礎
 */
function watchArray (arr, path, observer) {
    def(arr, '__observer__', observer)
    observer.path = path
    if (hasProto) {
        arr.__proto__ = ArrayProxy
    } else {
        for (var key in ArrayProxy) {
            def(arr, key, ArrayProxy[key])
        }
    }
}

/*
 *  具體實現對象監聽的方法  
*/
function bind (obj, key, path, observer) {
    var val       = obj[key],
        watchable = isWatchable(val),
        values    = observer.values,
        fullKey   = (path ? path + '.' : '') + key
    values[fullKey] = val
    // 觸發set事件
    observer.emit('set', fullKey, val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        get: function () {
            // only emit get on tip values
            if (depsOb.active && !watchable) {
                observer.emit('get', fullKey)
            }
            return values[fullKey]
        },
        set: function (newVal) {
            values[fullKey] = newVal
            ensurePaths(key, newVal, values)
            observer.emit('set', fullKey, newVal)
            // 被賦值,監聽新對象
            watch(newVal, fullKey, observer)
        }
    })
    watch(val, fullKey, observer)
}

/**
 *  只監聽數組和對象
 */
function isWatchable (obj) {
    var type = typeOf(obj)
    return type === 'Object' || type === 'Array'
}

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()