拷貝
復(fù)制與拷貝
let user = {
name: "John"
};
let user2=user; //變量名復(fù)制,只是持有了源對象的引用
let userClone=clone(user);//對象克隆熬尺,新對象是是源對象的拷貝
復(fù)制:將一個對象a賦值給另一個變量b摸屠,這個只是存儲了對象a的引用地址,是屬于同一個對象
克铝缓摺:創(chuàng)建一份獨(dú)立的對象拷貝季二,新對象具有源對象項(xiàng)的所有可枚舉屬性(值),兩個對象之間相互獨(dú)立
淺拷貝
思路:聲明一個新對象揭措,將源對象的可枚舉屬性(值)拷貝到新對象上
實(shí)現(xiàn)方式
-
for...in
復(fù)制所有屬性值
- 會拷貝對象自身以及其原型鏈上的可枚舉屬性
let dest = {}; // 新的空對象
// 復(fù)制所有的屬性值
for (let key in src) {
dest[key] = src[key];
}
- 采用jQuery使用extend,
jQuery.extent(dest,src)
以默認(rèn)配置為優(yōu)先胯舷,用戶設(shè)置為覆蓋
賦值對象的可枚舉屬性
- 會拷貝對象自身以及其原型鏈上的可枚舉屬性
- 無法處理值為undefined的屬性/值
- 只拷貝對象中基本數(shù)據(jù)類型的屬性,對于引用數(shù)據(jù)類型的數(shù)據(jù)會保持對象引用绊含,
-
Object.assign(dest,[ src1, src2, src3...])
,將 src1, ..., srcN 這些所有的對象復(fù)制到 dest
- 只拷貝對象中基本數(shù)據(jù)類型的屬性需纳,對于引用數(shù)據(jù)類型的數(shù)據(jù)會保持對象引用
- 如果目標(biāo)對象中的屬性具有相同的鍵,則屬性將被源對象中的屬性覆蓋艺挪。后面的源對象的屬性將類似地覆蓋前面的源對象的屬性。
- 只會拷貝源對象自身可枚舉的屬性到目標(biāo)對象。該方法使用源對象的
[[Get]]
和目標(biāo)對象的[[Set]]
麻裳,所以它會調(diào)用相關(guān) getter 和 setter口蝠。因此,它分配屬性津坑,而不僅僅是復(fù)制或定義新的屬性妙蔗。如果合并源包含getter,這可能使其不適合將新屬性合并到原型中疆瑰。為了將屬性定義(包括其可枚舉性)復(fù)制到原型眉反,應(yīng)使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。- String類型和 Symbol 類型的屬性都會被拷貝穆役。
- 在出現(xiàn)錯誤的情況下寸五,例如,如果屬性不可寫耿币,會引發(fā)TypeError梳杏,如果在引發(fā)錯誤之前添加了任何屬性,則可以更改target對象淹接。
- 不會在那些src對象值為 null或 undefined 的時候拋出錯誤十性。
- 原始類型會被包裝為對象
總結(jié)
無法正常處理屬性(值)為引用類型的數(shù)據(jù),
深拷貝
思路:復(fù)制的時候應(yīng)該檢查
obj[key]
的每一個值塑悼,如果它是一個對象劲适,那么把它也復(fù)制一遍
實(shí)現(xiàn)方式
jQuery.extend(true,dest,src)
,會遞歸處理對象的中引用數(shù)據(jù)類型屬性(值)JSON.parse(JSON.stringify(obj))
- 無法拷貝對象中
Function類型
的屬性 - 無法拷貝對象中值為undefined的屬性
- 無法拷貝具有循環(huán)引用的對象(可用來檢測對象是否循環(huán)引用)
- 基于遞歸實(shí)現(xiàn)
var deepClone=function(obj) {
// 處理數(shù)組
if(isArray(obj)){
return obj.map(function(ele) {
return isArray(ele)||isObject(ele)?deepClone(ele):ele
})
} else if(isObject(obj)){
return reduce(obj,function(memo,value,key) {
memo[key]=isArray(value)||isObject(value)?deepClone(value):value
return memo
},{})
}else {
return obj
}
}
以上版本并未處理循環(huán)引用問題,以及特殊的引用數(shù)據(jù)類型(Set/Map/RegExp等)
循環(huán)引用
我們先來看個例子
var man = {
name: 'amsterdam',
sex: 'male'
};
man['father'] = man;
對象man
的屬性father
又指向了man
本身,形成了“環(huán)”厢蒜,如果不能正常處理此類情況霞势,將出現(xiàn)調(diào)用棧溢出。
有一個標(biāo)準(zhǔn)的深拷貝算法郭怪,用于解決上面這種和一些更復(fù)雜的情況支示,叫做 結(jié)構(gòu)化克隆算法(Structured cloning algorithm)。
算法的優(yōu)點(diǎn)是:
- 可以復(fù)制 RegExp 對象鄙才。
- 可以復(fù)制 Blob颂鸿、File 以及 FileList 對象。
- 可以復(fù)制 ImageData 對象攒庵。CanvasPixelArray 的克隆粒度將會跟原始對象相同嘴纺,并且復(fù)制出來相同的像素數(shù)據(jù)。
- 可以正確的復(fù)制有循環(huán)引用的對象
依然存在的缺陷是:
Error 以及 Function 對象是不能被結(jié)構(gòu)化克隆算法復(fù)制的浓冒;如果你嘗試這樣子去做栽渴,這會導(dǎo)致拋出 DATA_CLONE_ERR 的異常。
企圖去克隆 DOM 節(jié)點(diǎn)同樣會拋出 DATA_CLONE_ERROR 異常稳懒。
-
對象的某些特定參數(shù)也不會被保留
- RegExp 對象的 lastIndex 字段不會被保留
- 屬性描述符闲擦,setters 以及 getters(以及其他類似元數(shù)據(jù)的功能)同樣不會被復(fù)制。例如,如果一個對象用屬性描述符標(biāo)記為 read-only墅冷,它將會被復(fù)制為 read-write纯路,因?yàn)檫@是默認(rèn)的情況下。
- 原形鏈上的屬性也不會被追蹤以及復(fù)制寞忿。
可參考lodash等庫函數(shù)的實(shí)現(xiàn)
手動實(shí)現(xiàn)深拷貝
const deepCloneClourse = (target) => {
let cached = new WeakMap()
function baseClone (obj) {
let objectType = getType(obj)
let cloneObj
// 檢測對象是否已克隆 返回克隆后的對象
let temp = cache(cached, obj)
if (temp) {
return temp
}
switch (objectType) {
// Object
case 'Object':
//緩存已克隆對象
cached.set(obj, cloneObj = {})
//key-value 類型中Key可能是symbol
Object.getOwnPropertySymbols(obj).forEach(item => {
let symbol = Object(Symbol.prototype.valueOf.call(item))
cloneObj[symbol] = baseClone(obj[item])
})
break
// 容器類
case 'Set':
//緩存已克隆對象
cached.set(obj, cloneObj = new Set())
obj.forEach((val) => {
cloneObj.add(baseClone(val, cached))
})
break
case 'Map':
//緩存已克隆對象
cached.set(obj, cloneObj = new Map())
obj.forEach((val, key) => {
cloneObj.set(key, baseClone(val))
})
//key-value 類型中Key可能是symbol
Object.getOwnPropertySymbols(obj).forEach(item => {
let symbol = Object(Symbol.prototype.valueOf.call(item))
cloneObj[symbol] = baseClone(obj[item])
})
break
case 'Array':
//緩存已克隆對象
cached.set(obj, cloneObj = [])
obj.forEach((val) => {
cloneObj.push(baseClone(val))
})
break
// 普通對象
case 'RegExp':
cloneObj = new RegExp(obj.source, obj.flags)
break
case 'Date':
cloneObj = new Date(obj)
break
case 'Symbol':
cloneObj = Object(Symbol.prototype.valueOf.call(obj))
break
case 'Boolean':
cloneObj = Boolean(obj)
break
case 'Function':
cloneObj = function () {
return obj.apply(this, arguments)
}
break
default://null undefined NaN string number boolean
cloneObj = obj
}
if (typeof obj === 'object') {
for (let item in obj) {
if (obj.hasOwnProperty(item)) {
cloneObj[item] = baseClone(obj[item])
}
}
}
return cloneObj
}
return baseClone(target)
}
總結(jié)
- 在實(shí)際開發(fā)過程中驰唬,我們可以預(yù)估對象的基本結(jié)構(gòu),正確的使用深淺拷貝腔彰,避免在函數(shù)中因修改對象值照成數(shù)據(jù)異常的情形叫编。
- 大而全的東西,往往是最昂貴的霹抛。