jQuery的$.extend
方法是我們在開發(fā)中經(jīng)常用到的方法,用于合并若干個對象,且支持深度拷貝。
最常見的一個使用場景是參數(shù)的合并错负,比如我們要做一個顯示對話框的組件坟瓢,接收一個option
對象參數(shù),把它和默認(rèn)參數(shù)defaultOption
合并湿颅,得到新的參數(shù)载绿。這樣做的好處就是對option
字段的拓展非常方便,并且使用者可以只傳部分參數(shù)油航,其他均為默認(rèn)值,代碼可讀性也比較好怀浆。
var showDialog = (function() {
var defaultOption = {
title:'',
width:500,
close:function(){}
}
return function(option) {
$.extend({},defaultOption,option);
}
})()
showDialog({
title:'',
close:function() {
console.log('dialog closed')
}
})
這種模式在很多地方都用到谊囚,最常見的我們使用$.ajax
發(fā)起ajax請求,對于傳遞的option也是這樣處理的执赡。
在現(xiàn)在的項(xiàng)目中镰踏,由于用的是Vue,避免了繁瑣的Dom操作沙合,所以用不到j(luò)Query提供的dom操作奠伪。但是我需要$.extend
方法。在查看了他的源碼之后首懈,本來打算直接copy過來使用绊率,可是發(fā)現(xiàn)它有很多依賴項(xiàng),懶得一一去找究履,所以索性自己從頭寫一個滤否。
我們可以考慮首先實(shí)現(xiàn)一個最簡單的extend函數(shù),即用for in
遍歷源對象最仑,覆蓋目標(biāo)對象的對應(yīng)屬性即可藐俺。
var extend = function(destination,source) {
for(var property in source) {
destination[property] = source[property]
}
return destination
}
非常簡潔易懂,這種實(shí)現(xiàn)方式滿足了大部分情況下的需求泥彤,但存在一個問題欲芹,就是這種合并是淺拷貝。
如果合并的屬性中含有對象a
吟吝,那么在進(jìn)行合并之后菱父,destination
擁有的是對象a的引用,而source
對象也有對象a
的引用爸黄,那么如果我們修改對象a
的屬性滞伟,destination
和source
都將被修改——它們引用的是同一個對象,這就是淺拷貝炕贵。我們想實(shí)現(xiàn)深拷貝梆奈,即在destination
中的對象a
是一份復(fù)制品,而不是引用称开,那么我們需要對對象的賦值做額外的判斷和處理亩钟。
var isObjFunc = function(name) {
var toString = Object.prototype.toString
return function() {
return toString.call(arguments[0]) === '[object ' + name + ']'
}
}
var isObject = isObjFunc('Object'),
var extend = function(destination,source,isDeep) {
var obj,copy
for(var property in source) {
obj = source[property]
if(isDeep && isObject(obj) { // 判斷是深拷貝且這個屬性是純對象
var copy = {}
destination[property] = extend(copy,obj,isDeep) // 遞歸調(diào)用乓梨,創(chuàng)建一份obj的拷貝,賦值給destination
} else {
destination[property] = obj
}
}
return destination
}
上面的代碼就實(shí)現(xiàn)了一個簡單深拷貝清酥。但這里還有一個漏洞扶镀,如果是數(shù)組的話,創(chuàng)建copy
的時候應(yīng)該設(shè)置為一個新的空數(shù)組焰轻,這樣for in
操作擴(kuò)展才能正常執(zhí)行臭觉。再參考jQuery.extend
的實(shí)現(xiàn)方式,利用arguments
處理多個對象合并的情況辱志,最終的代碼如下蝠筑,較為完整的實(shí)現(xiàn)了extend
,供參考揩懒。如果有bug歡迎留言指正什乙。
var extend = (function() {
var isObjFunc = function(name) {
var toString = Object.prototype.toString
return function() {
return toString.call(arguments[0]) === '[object ' + name + ']'
}
}
var isObject = isObjFunc('Object'),
isArray = isObjFunc('Array'),
isBoolean = isObjFunc('Boolean')
return function extend() {
var index = 0,isDeep = false,obj,copy,destination,source,i
if(isBoolean(arguments[0])) {
index = 1
isDeep = arguments[0]
}
for(i = arguments.length - 1;i>index;i--) {
destination = arguments[i - 1]
source = arguments[i]
if(isObject(source) || isArray(source)) {
console.log(source)
for(var property in source) {
obj = source[property]
if(isDeep && ( isObject(obj) || isArray(obj) ) ) {
copy = isObject(obj) ? {} : []
var extended = extend(isDeep,copy,obj)
destination[property] = extended
}else {
destination[property] = source[property]
}
}
} else {
destination = source
}
}
return destination
}
})()
測試代碼如下
var a = {name:1}
var b = {name:2}
var c = {name:3}
extend(true,a,b,{name:[a,b,c],value:a})
console.log(a)
console.log(a.name[0] === a) // false
console.log(a.value === a) // false