JS 对象的拷贝

JS 对象的拷贝,有两种方式,一种是深拷贝(deep copy),一种是浅拷贝(shallow copy)。其实我不是很喜欢这种说法,我更喜欢对象克隆(clone)和复制引用(reference copy)的说法。

复制引用

如其名曰,复制引用只是复制对这个对象的引用,例如:

var obj = {
  a: 1,
};

var newObj = obj;

这个时候,obj 和 newObj 实质上都保存着对同一个对象的引用,有点像 C 当中的指针的概念,obj 和 newObj 保存着同一个对象的地址,因此任何对 newObj 的操作,都会反映在 obj 上:

newObj.a = 2;
console.log(obj.a); // 2

不过对于包装类型:Number, String, Boolean 来说,上面这种做法是无效的,因为他们只会拷贝值,并不会拷贝对其的引用,试想,如果连这些原始类型都能拷贝引用了,那么代码就根本没法维护了,时时刻刻都有可能误操作到别的对象上了。

对象克隆

对象克隆本质上就是创建一个新的空对象,然后遍历源对象(要克隆的那个对象)的属性,然后把源对象的属性都一一复制到新的对象上,这样就完成了对源对象的克隆。

我们还需要检测源对象的属性,如果它的属性也是一个对象,我们就要利用递归来进行深层次的拷贝,如果是原始类型, 直接复制到新对象上即可。

同时,还要防止循环引用,也就是 a.b = a 的情况,这种情况会引起死循环,所以我们也要对其进行检测。

function clone(obj) {
  var name;
  var copy = {};

  // 如果穿进来的参数不是一个对象或者函数
  if (typeof obj !== 'object' && typeof obj !== 'function') {
    return {};
  }

  // 如果对象的属性是数组
  if (obj instanceof Array) {
    return Array.prototype.slice.call(obj);
  }

  for (name in obj) {
    // 防止死循环
    if (obj[name] === obj) {
      continue;
    }
    if (obj[name] && typeof obj[name] === 'object') {
      copy[name] = clone(obj[name]);
    } else if (obj[name]) {
      copy[name] = obj[name];
    }
  }
  return copy;
}

这样我们就完成了比较基本的对象克隆:

var bar = {
  a: {
    inner: 123,
    inside: {
      test: 'you can see me',
    },
  },
  b: [1, 2, 3],
  c: 'string',
  d: function(a, b) {
    return a + b;
  },
};

var foo = clone(bar);
console.log(foo);

输出:

{ a: { inner: 123, inside: { test: 'you can see me' } },
  b: [ 1, 2, 3 ],
  c: 'string',
  d: [Function: d] }