实现监听数组方法
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'
}