JS對象的引用複製和數據複製

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 ] }

賦值後操作相互不影響,目的已經達到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章