彻底搞懂js深浅拷贝

175 阅读2分钟

前段时间面试遇到一个我觉得有必要深入了解的问题,关于js浅拷贝和深拷贝的问题

js的变量类型

1. 基本类型

Udefines、Null、Boolean、Number和String五种基本数据类型的变量是按值存放的,存放在栈内存中的简单数据段

2. 引用类型

存放在堆内存中的对象,变量保存的是一个指针,指针指向另一个位置。如数组、对象。

浅拷贝的实现

对于基本类型,直接引用赋值即是深拷贝,而对于引用类型则是浅拷贝,例如:

function shallowClone(copyObj) {
 var obj = {};
 for ( var i in copyObj) {
 obj[i] = copyObj[i];
 }
 return obj;
}
var x = {
 a: 1,
 b: { f: { g: 1 } },
 c: [ 1, 2, 3 ]
};
var y = shallowClone(x);
console.log(y.b.f === x.b.f);  // true

上述结果表示对象直接赋值实际上是赋给新对象一个指针指向原来的对象,那么关于引用类型的深复制如何实现呢?

深拷贝的实现

在上一篇博文中刚好写了callee和caller,本来我还觉得这俩货无用武之地,现在看来是自己肤浅了。在使用递归的时候callee还是很好用的,例如实现一个深拷贝:

function deepClone(initalObj, finalObj) {
    var obj = finalObj || {};
    for (var i in initalObj) {
        var prop = initalObj[i];
  
        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
        if(prop === obj) {
            continue;
        }
  
        if (typeof prop === 'object') {
            obj[i] = (prop.constructor === Array) ? [] : {};
            arguments.callee(prop, obj[i]);
        } else {
            obj[i] = prop;
        }
    }
    return obj;
}

其他库对深浅拷贝的支持

  • jquery

在jquery中有$.clone()$.extend(),看上去属于拷贝的两个函数,但$.clone()并不用于js对象的深复制,而是用于dom对象。那么$.extend()这个函数就牛啦,就是用来实现深浅复制的。使用方式形如$.extend(true,{},...),其中第一个参数为true则为深复制,例子如下:

var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};

var y = $.extend({}, x),          //shallow copy
    z = $.extend(true, {}, x);    //deep copy

y.b.f === x.b.f       // true
z.b.f === x.b.f       // false
  • underscore

在underscore中有_.clone()这样一个方法,实际上是一种浅复制,所有嵌套的对象和数组都是直接复制引用而并没有进行深复制 源码是这么写的:

// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
  • lodash

实际上在有了lodash以后我就不再使用underscore了,在lodash中有两个复制的方法:_.clone()_.cloneDeep(),其中_clone(obj,true)等价于_.cloneDeep(obj)

  • es6

在es6中有个函数是object.assign(),但是很抱歉,实现的是浅复制,例如

Object.assign({a: {b: 0}}, {a: {b: 1, c: 2}}, {a: {c: 3}});
//{a: {c: 3}