【js面试题】深 / 浅拷贝大揭秘:告别数据复制混乱,轻松搞定面试官

35 阅读4分钟

在 JavaScript 编程的世界里,数据拷贝是我们经常会碰到的操作。其中,深拷贝和浅拷贝就像两个小伙伴,各有各的特点。今天,咱们就来好好认识一下它们。

一、浅拷贝:简单的复制表层

浅拷贝呢,就像是给一个东西做了个表面的复制。打个比方,你有一个装满各种东西的盒子,浅拷贝这个盒子,就是再弄出一个新盒子,把原来盒子里的东西一件件拿出来放进去。如果原来盒子里装的是普通的小物件,比如糖果(对应基本数据类型),那新盒子里的糖果就是完全独立的复制。但要是原来盒子里装的是一个小盒子(对应引用数据类型,像对象或数组),浅拷贝就只是把这个小盒子的位置记录下来放到新盒子里,并没有真的再复制一个小盒子。

(一)Object.assign () 方法:合并式的浅拷贝

const source = {
  name: '小明',
  hobbies: ['画画', '唱歌']
};

const target = Object.assign({}, source);

source.hobbies.push('跳舞');

console.log(target.hobbies); // ["画画", "唱歌", "跳舞"]

你看,我们用 Object.assign() 把 source 浅拷贝到 target。然后给 source 的 hobbies 数组加了个 “跳舞”,结果 target 的 hobbies 数组也变了。这就是因为它们的 hobbies 数组其实是同一个,只是在不同的 “盒子” 里记录了位置。

(二)扩展运算符(...):便捷的浅拷贝 扩展运算符用起来很方便,也能做浅拷贝哦。

const source2 = {
  name: '小红',
  address: {
    city: '北京'
  }
};

const target2 = {...source2 };

source2.address.city = '上海';

console.log(target2.address.city); // "上海"

这里,target2 是 source2 的浅拷贝。当我们改了 source2 里嵌套的 address 对象中的 city,target2 里的也跟着变了,因为它们共享同一个 address 对象的引用。

二、深拷贝:彻彻底底的复制

深拷贝就不一样啦,它是要完完全全复制出一个一模一样的东西,就像克隆一样。不管原来的东西里面嵌套了多少层小盒子,深拷贝都会一个个把它们都复制出来,新的和原来的完全独立,互不干扰。 (一)JSON.parse (JSON.stringify ()) 方法:简单但有限的深拷贝

这个方法就像是把东西先变成一个特殊的图纸(JSON 字符串),然后再按照图纸重新做出一个新东西。

const source3 = {
  name: '小刚',
  details: {
    age: 10,
    hobbies: ['足球', '篮球']
  }
};

const target3 = JSON.parse(JSON.stringify(source3));

source3.details.age = 12;

console.log(target3.details.age); // 10

这样看起来好像很不错,把 source3 深拷贝到了 target3,改了 source3 的年龄,target3 没受影响。但是呢,这个方法有小脾气哦。它搞不定函数、Symbol 类型的数据,要是碰到有循环引用的对象(比如一个对象里嵌套自己),它就会晕头转向,不知道该怎么办啦。 (二)递归实现深拷贝:手动打造全面的深拷贝

我们可以自己写个函数,用递归的方法来做深拷贝。就像是一个勤劳的小工匠,一层一层地把东西都复制好。

function deepCopy(obj) {
  if (obj === null || typeof obj!== 'object') {
    return obj;
  }

  let result;
  if (obj instanceof Array) {
    result = [];
  } else {
    result = {};
  }

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepCopy(obj[key]);
    }
  }

  return result;
}

const source4 = {
  name: '小美',
  info: {
    job: '学生',
    skills: ['语文', '数学']
  }
};

const target4 = deepCopy(source4);

source4.info.job = '班长';

console.log(target4.info.job); // "学生"

这个 deepCopy 函数就比较厉害啦,能把嵌套的对象和数组都正确地深拷贝。不过呢,像 Date 对象、RegExp 对象这些特殊的对象,它还需要再加工一下才能完美复制。

三、总结

浅拷贝和深拷贝在 JavaScript 里都很重要。浅拷贝简单快速,但是对于嵌套的引用数据类型可能会有数据关联的问题。深拷贝虽然能完全独立复制数据,但有些方法有局限性,自己写递归函数又要考虑很多特殊情况。 在实际写代码的时候,我们要看看数据是什么样的。如果数据比较简单,没有太多嵌套的引用类型,浅拷贝可能就够了。要是数据很复杂,有很多嵌套的对象和数组,而且需要数据之间完全不互相影响,那就要选合适的深拷贝方法啦,这样才能让我们的程序稳稳地运行,数据也不会乱套。