实现监听数组方法

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'
}
打赏支持我写出更多好文章,谢谢!
打赏作者
+

(^_^)打个赏喝个咖啡(^_^)

微信支付
支付宝支付
Posted on 2017-04-23 15:16  追梦子  阅读(720)  评论(0编辑  收藏  举报