面试题 | 请实现一个深拷贝

628 阅读2分钟

先来看下基础的数据类型和浅拷贝。

JavaScript 数据类型

基本数据类型

  • String
  • Number
  • Null
  • Undefined
  • Boolean
  • Symbol(ES6 新增)

基本数据类型存放在栈内存中,可直接访问和修改变量的值,基本数据类型不存在拷贝。

引用类型

  • Object

引用类型值为对象,存放在堆内存中。

在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址。当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据。

浅拷贝

基础的引用拷贝

var source=[1,2,3]
var target=source
source[0]=0
console.log(source,target) // [ 0, 2, 3 ] [ 0, 2, 3 ]

因为 source 数组是 Object 类型,是引用类型,target 相当于多了一个指针,指针指向的值变了,所以两个数组都改变。

concat、slice、Object.assign()

var source=[1,2,3]
var target=source.concat()
source[0]=0
console.log(source,target) // [ 0, 2, 3 ] [ 1, 2, 3 ]

这个像深拷贝吗?是深拷贝吗?

看下面这个输出:

var source=[1,2,[3,4],{name:'linmu'}]
var target=source.concat()
source[3].name='beiyang'
console.log(source[3],target[3]) // {name:'beiyang'}  {name:'beiyang'}

当数组 source 包含对象时,slice 和 concat 拷贝出来的数组中的对象还是共享同一个内存地址,所以仍是浅拷贝。

深拷贝

深拷贝是可以完美的解决浅拷贝的弊端,重新开辟一块地址,两者互不关联。

普通深拷贝

JSON 对象是 ES5 中引入的新的类型(支持的浏览器为 IE8+),JSON 对象 parse 方法可以将 JSON 字符串反序列化成 JS 对象,stringify 方法可以将 JS 对象序列化成 JSON 字符串,借助这两个方法,可以实现简单对象的深拷贝,但不能处理函数、正则等对象。

JSON.parse(JSON.stringify())

最强深拷贝

可解决上面不能处理函数,setter,getter 等问题,方便在面试时手写:

function cloneObject (target, source) {
  //获取对象下的所有属性,包括不可遍历属性
  var names = Object.getOwnPropertyNames(source);
  for (var i = 0; i < names.length; i++) {
    // 获取每个属性的描述信息
    var desc = Object.getOwnPropertyDescriptor(source, names[i]);
    if (typeof (desc.value) === "object" && desc.value !== null) {
      var obj;
      if (Array.isArray(desc.value)) {
        obj = [];
      } else {
        obj = {};
      }
      // 如果 desc 的值是对象的话,递归调用,直至属性为费对象
      Object.defineProperty(target, names[i], {
        configurable: desc.configurable,
        enumerable: desc.enumerable,
        value: obj,
        writable: desc.writable
      });
      cloneObject(obj, desc.value);
    } else {
      // 如果 desc 的值不是对象,直接声明
      Object.defineProperty(target, names[i], desc);
    }
  }
  return target;
}