淺拷貝與深拷貝

2022-11-26 12:00:46

一、資料型別儲存

  • 在JavaScript中存在兩巨量資料型別:基本型別、參照型別。

    • 基本資料型別存放在棧中,是一段簡單的資料段,資料大小確定,記憶體空間大小可以分配,是直接按值存放的,可以按值存取

    • 參照資料型別存放在堆中,變數在棧中儲存的是指向堆記憶體的地址值,這個地址值指向對應的物件型別,存取堆記憶體中的物件是通過地址值存取的。

二、淺拷貝

  • 淺拷貝,指的是建立新的資料,這個資料有著原始資料屬性值的一份精確拷貝。

  • 如果屬性是基本型別,拷貝的就是基本型別的值。如果屬性是參照型別,拷貝的就是記憶體地址。

  • 即淺拷貝是拷貝一層。

  • 下面簡單實現一個淺拷貝:

    function shallowClone (obj) {
    	const newObj = {}
    	for (let prop in obj) {
    		if (obj.hasOwnProperty(prop)) {
    			newObj[prop] = obj[prop]
    		}
    	}
    	return newObj
    }
    
  • 在JavaScript中,存在淺拷貝的現象有:

    • Object.assign()
    • Array.prototype.slice()
    • Array.prototype.concat()
    • 使用擴充套件運運算元實現的複製
  • Object.assign()

    var obj = {
        age: 18,
        nature: ['smart', 'good'],
        names: {
            name1: 'fx',
            name2: 'xka'
        },
        love: function () {
            console.log('fx is a great girl')
        }
    }
    var newObj = Object.assign({}, fxObj)
    
  • slice()

    const fxArr = ['One', 'Two', 'Three']
    const fxArrs = fxArr.slice(0)
    fxArrs[1] = 'love'
    consloe.log(fxArr)	// ['One', 'Two', 'Three']
    consloe.log(fxArrs)	// ['One', 'love', 'Three']
    
  • concat()

    const fxArr = ['One', 'Two', 'Three']
    const fxArrs = fxArr.concat()
    fxArrs[1] = 'love'
    consloe.log(fxArr)	// ['One', 'Two', 'Three']
    consloe.log(fxArrs)	// ['One', 'love', 'Three']
    
  • 擴充套件運運算元

    const fxArr = ['One', 'Two', 'Three']
    const fxArrs = [...fxArr]
    fxArrs[1] = 'love'
    consloe.log(fxArr)	// ['One', 'Two', 'Three']
    consloe.log(fxArrs)	// ['One', 'love', 'Three']
    

三、深拷貝

  • 深拷貝開闢一個新的棧,兩個物件相同,但是對應兩個不同的地址,修改一個物件的屬性,不會改變另一個物件的屬性。

  • 常見的深拷貝方式有:

    • _.cloneDeep()
    • jQuery.extend()
    • JSON.stringify()
    • 迴圈遞迴
  • _.cloneDeep()

    const _ = require('lodash')
    const obj1 = {
    	a: 1,
    	b: { f: { g: 1 } },
    	c: { 1, 2, 3 }
    }
    const obj2 = _.cloneDeep(obj1)
    console.log(obj1.b.f === obj2.b.f)	// false
    
  • jQuery.extend()

    const $ = require('jquery')
    const obj1 = {
    	a: 1,
    	b: { f: { g: 1 } },
    	c: { 1, 2, 3 }
    }
    const obj2 = $.extend(true, {}, obj1)
    console.log(obj1.b.f === obj2.b.f)	// false
    
  • JSON.stringify()

    const obj = {
        name: 'A',
        name1: 'undefined',
        name2: function () {},
        name3:	Symbol('A')
    }
    const obj2 = JSON.parse(JSON.stringify(obj1))	// 會忽略undefined、Symbol、函數
    console.log(obj2)	// { name: 'A' }
    
  • 迴圈遞迴

    function deepClone (obj, hash = new WeakMap()) {
        if (obj === null) return obj	// 如果是null或者undefined,就不進行拷貝操作
        if (obj instanceof Date) return new Date(obj)
        if (obj instanceof RegExp) return new RegExp(obj)
        // 可能是物件或者普通的值,如果是函數的話不需要深拷貝
        if (typeof obj !== 'Object') return obj
        // 如果是物件,就進行深拷貝
        if (hash.get(obj)) return hash.get(obj)
        let cloneObj = new obj.constructor()
        // 找到的是所屬型別原型上的constructor,而原型上的constructor指向的是當前類本身
        hash.set(obj, cloneObj)
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                // 實現一個遞迴拷貝
                cloneObj[key] = deepClone(obj[key], hash)
            }
        }
        return cloneObj
    }
    

四、區別

  • 淺拷貝只複製記憶體地址,而不復制物件本身,新舊物件還是共用同一塊記憶體,修改物件屬性會影響原物件。

    // 淺拷貝
    const obj1 = {
        name: 'init',
        arr: [1, [2, 3], 4]
    }
    const obj3 = shallowClone(obj1)	// 一個淺拷貝方法
    obj3.name = 'update‘
    obj3.arr[1] = [5, 6, 7]	// 新舊物件還是共用同一塊記憶體
    console.log('obj1', obj1)	// obj1 { name: 'init', arr: [1, [5, 6, 7], 4] }
    console.log('obj3', obj3)	// obj3 { name: 'update', arr: [1, [5, 6, 7], 4] }
    
  • 深拷貝會另外創造一個一模一樣的物件,新物件與原物件不共用記憶體,修改新物件不會改到原物件。

    // 深拷貝
    const obj1 = {
        name: 'init',
        arr: [1, [2, 3], 4]
    }
    const obj4 = deepClone(obj1)	// 一個深拷貝方法
    obj4.name = 'update‘
    obj4.arr[1] = [5, 6, 7]	// 新物件與原物件不共用記憶體
    console.log('obj1', obj1)	// obj1 { name: 'init', arr: [1, [2, 3], 4] }
    console.log('obj4', obj4)	// obj4 { name: 'update', arr: [1, [5, 6, 7], 4] }
    
  • 當拷貝型別為參照型別時:

    • 淺拷貝是拷貝一層,屬性為物件時,淺拷貝是複製,兩個物件指向同一個地址。
    • 深拷貝是遞迴拷貝深層次,屬性為物件時,深拷貝是新開棧,兩個物件指向不同的地址。