javascript深拷贝和浅拷贝的区别

86 阅读3分钟

面试被问道深拷贝和浅拷贝,平常只知道用,没去追究差别的我,只能凭着最近的学习回答:"这两个之间最大的差别应该和数据的引用地址相关。"

等面试结束后,再去查,发现我说的不搭边。

为什么会有两种拷贝?

浅拷贝在某些情况下可能更高效,因为它只是复制了引用,而不需要递归复制所有的属性或元素。 而深拷贝则可以保证原始对象和拷贝对象之间的完全独立性,避免了原始对象的变动对拷贝对象造成的影响。

  1. 基本数据类型(Primitive Data Types):

    • 字符串(String):表示文本数据。
    • 数字(Number):表示数字。
    • 布尔值(Boolean):表示true或false。
    • undefined:表示未定义的值。
    • null:表示空值。
    • Symbol(ES6新增):表示唯一的标识符。
  2. 引用数据类型(Reference Data Types):

    • 对象(Object):表示复杂的数据结构。
    • 数组(Array):表示有序的数据集合。
    • 函数(Function):表示可执行的代码块。
    • 日期(Date):表示日期和时间。
    • 正则表达式(RegExp):表示用于模式匹配的文本字符串。
  • 基本数据类型存储在栈内存中,而引用数据类型存储在堆内存中。
  • 基本数据类型是按值访问的,每个变量都拥有自己的值,修改一个变量不会影响到其他变量。
  • 引用数据类型是按引用访问的,多个变量指向同一个对象,修改其中一个变量会影响到其他变量。

都怎么使用?

浅拷贝使用方法:

  1. 直接相等 let a=1; let b=a;
  2. Array.slice();
  3. Array.from();
  4. Array.concat()
  5. Object.assing()
let a = 2;
let b = a;
console.log(b) // 2
const list = [1,2,3]
const list1 = list.slice()
const list2 = Array.from(list)
const list3 = list.concat()
const list4 = Object.assign(list)
const person = {
    name: 'Tom',
    age: 2,
    hobby: ['drawing', 'play basketball']
}
const person1 = person
const person2 = Object.assign(person)
console.log(person1) // {name: 'Tom', age: 2, hobby: Array(2)}
person.hobby.push('play the guitar')
console.log(person1) // // {name: 'Tom', age: 2, hobby: Array(3)}
console.log(person2) // // {name: 'Tom', age: 2, hobby: Array(3)}
console.log(list1) // list = [1,2,3]

深拷贝使用方法

  1. 使用JSON.parse(JSON.stringify())进行深拷贝,但该方法有一些限制,例如无法拷贝函数、循环引用等。
  2. 使用递归函数自行实现深拷贝。
  3. 使用第三方库,如lodash的cloneDeep()方法。
    const person = {
        name: 'Tom',
        age: 2,
        hobby: ['drawing'],
        play: function () {
            console.log('can play the guitar')
        }
    }
    const person1 = JSON.parse(JSON.stringify(person))
    person.hobby.push('play basketball')
    console.log(person)  // {name: 'Tom', age: 2, hobby: Array(2), play: ƒ}
    console.log(person1) // {name: 'Tom', age: 2, hobby: Array(1)}
    
    function deepCopy (obj, map = new WeakMap()) {
        // 基本数据类型直接返回
        if (typeof obj !== 'object' && obj === null) {
            return obj
        }
        // 函数 正则 日期 map, set 执行对应构造,返回新的对象
        const constructor = obj.constructor
        if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) {
            return new constructor(obj)
        }
        // 解决共同引用 循环引用问题
        // 用 WeakMap 记录每次复制过的对象, 在递归过程中, 如果遇到已经复制过的对象, 
        // 则直接使用上次拷贝的对象, 不重新拷贝
        if (map.get(obj)) {
            return map.get(obj)
        }
        // 创建新对象
        const newObj = Array.isArray(obj) ? [] : {}
        map.set(obj, newObj)
        // 循环加递归
        let keys = Object.keys(obj), key = null, val = null;
        for (let i=0; i<keys.length; i++) {
            key = keys[i]
            val = obj[key]
            if (val && typeof val === 'object') {
                newObj[key] = deepCopy(val, map)
            } else {
                newObj[key] = val
            }
        }
        return newObj
    }

总结:

  1. 深拷贝和浅拷贝的用法不同:浅拷贝用于基本数据类型的拷贝,深拷贝用于复杂类型的拷贝;
  2. 产生的效果不同,浅拷贝,拷贝的是个引用,当源数据变了后,拷贝后的数据也会变化;但深拷贝是另外再new了个值出来,源数据的变化和拷贝后的数据不会变化。