JS對象分爲兩類,一類爲基礎類型對象,包括字符串(String),數值(Number),布爾值(Boolean),null(空),undefined(未定義)。另一類爲合成對象,又叫做引用類型對象,包括數組(Array),對象(Object),函數(Function)。
基礎類型對象存儲的是對象的實際數據,引用類型對象存儲的是對象的引用地址,而把對象的實際內容單獨存放,因爲引用對象通常比較龐大,這是數據開銷和內存開銷優化的手段。
基礎對象的賦值,實際複製的是對象本身的數據:
let a = 5
let b = a
b = 1
console.log(a) // => 5
console.log(b) // => 1
// a爲基礎類型對象,存儲的爲數據本身,所以賦值給b後,b存儲的也是數據本身
// 數據被複製爲兩份,a,b各一份,a,b各自的操作僅對自身有效.
引用類型對象的賦值,實際複製的是對象數據的引用,實際對象的內容單獨存儲在引用指向的地址:
let a = new Array(1,2,3)
let b = a
let c = a
b.push(4)
console.log(a) // => [1,2,3,4]
console.log(b) // => [1,2,3,4]
console.log(c) // => [1,2,3,4]
c[0] = 5
console.log(a) // => [5,2,3,4]
console.log(b) // => [5,2,3,4]
console.log(c) // => [5,2,3,4]
// 複合型對象即引用型對象複製的是對象的引用,b,c,分別複製了a的對象引用
// 實際a,b,c擁有相同的引用地址,地址指向的是同一個對象
// 當b,c分別做對象操作時,實際操作的仍舊是同一對象
如何避免引用型對象複製造成的數據關聯操作,達到真正賦值的目的:
// 通過對象數據的序列化和反序列化達到賦值的目的
let a = {x: 1, y: 2, z:3}
let b = JSON.stringify(a) // 序列化a賦值給b
let c = JSON.parse(b) // 反序列化b賦值給c
// 當然可以一步寫完,完全沒必要有b => let c = JSON.parse(JSON.stringify(a))
c.x = 5
console.log(c) // => {x: 5, y: 2, z:3}
console.log(a) // => {x: 1, y: 2, z:3}
// a的值成功的賦值給了c,並且c的操作已經對a沒有影響了
一般操作建議封裝函數批量處理此類問題,因爲比較常見,處理方法採用遞歸循環讀取對象屬性直至內部再無引用型對象存在:
// 所有對象在寫方法時都要當作爲多屬性對象,且對象中含有鍵指向其他對象
let obj = {
a: 1,
b: {
c: 2
d: 3
}
}
let x = (obj) => {
// 此處判斷方便遞歸時判斷鍵對應的值是否爲對象,如果不爲對象則直接返回
if (typeOf obj !== 'object') {
return obj
}
let newObj = {}
for (let key in obj) {
newObj[key] = x(obj[key]) // 此處自己調用自己,典型的遞歸
}
return newObj;
}
// 調用方法:
let newObj = x(obj)
以上僅考慮了對象,且僅考慮了對象的內部屬性爲對象的情況,實際使用中,對象內部的屬性可能指向的是一個對象,也可能是一個數組又或者是一個函數,由於函數可以直接克隆複製,不會關聯變化,暫不在考慮範圍內。不論是對象或者數組或都會導致賦值的不正確。同理,由於js中數組什麼都能存,數組中可能存放有對象,數組又或者是函數等,這些都需要遞歸查詢。
對於對象或者數組處理方式其實也很簡單,在上面示例中加入數組的判斷即可。if typeOf === object => 走對象賦值的邏輯,同時遞歸檢查,if instanceof(Array) => 走數組的賦值邏輯,同時遞歸檢查即可, typeOf無法檢查數組。
function copy (obj) {
// obj 非對象,也不是數組
if (typeof obj !== 'object' && !(obj instanceof Array)) {
return obj
}
let newArr = []
let newObj = {}
if (obj instanceof Array) {
for (let item of obj) {
newArr.push(copy(item))
}
return newArr
} else {
for (let item in obj) {
newObj[item] = copy(obj[item])
}
return newObj
}
}
let obj = {
a: '1',
b: 2,
c: {
d: '3',
e: 4,
g: {
h: '8',
i: [
{
j: 10,
k: 11
},
9
]
}
},
f: [5,6,7]
}
let c = copy(obj)
c.f[0] = 10
c.c.d = 'hello world'
c.c.g.h = 'woo'
c.c.g.i[0].j = 12
console.log(c) // => { a: '1',b: 2,c: { d: 'hello world', e: 4, g: { h: 'woo', i: [Array] } },f: [ 10, 6, 7 ] }
console.log(obj) // => { a: '1',b: 2,c: { d: '3', e: 4, g: { h: '8', i: [Array] } },f: [ 5, 6, 7 ] }
賦值後操作相互不影響,目的已經達到。