文章出處
文章列表
實現監聽數組方法
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'
}
文章列表
全站熱搜