JS深浅拷贝实现

  1. 引用赋值与浅拷贝的区别

引用赋值是地址的赋值,将对象指针赋值给一个变量,让此变量指向对象。

浅拷贝只会将对象的各个属性进行依次复制,并不会进行递归复制。

var obj = { a:1, arr: [2,3] };
var obj1 = {};
var shallowObj = {};
var deepObj = {};

//引用赋值,obj1指向obj所指向的对象
var obj1 = obj;

//浅拷贝实现
for (var prop in obj){
    if(obj.hasOwnProperty(prop)){
        shallowObj[prop] = obj[prop];
    }
}

//深拷贝,采用JSON.parse(JSON.stringify(obj))的方法实现,稍后讲解。
obj2 = JSON.parse(JSON.stringify(obj));

obj.arr[1] = 5;

console.log(obj1.arr[1]);//输出结果:5。其实,obj1与obj实为同一个对象
console.log(shallowObj.arr[1]);//输出结果:5。
console.log(deepObj.arr[1]); //输出结果:3。
复制代码
  1. 浅拷贝与深拷贝的区别

浅拷贝:因为浅拷贝只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript 存储对象都是存地址的,所以浅拷贝会导致 obj.arrshallowObj.arr 指向同一块内存地址,大概的示意图如下。意思就是将 obj 对象拷贝到 shallowObj 对象中,但不包括 obj 里面的子对象(arr),也就是说shallowObj 只保存了 obj 的子对象的指针,但同为一个子对象。

深拷贝:不仅复制原对象的各个属性,同时也将原对象各个属性所包含的子对象也递归地复制到新对象上。复制后两者是完全不相干的两个对象。将 obj 对象拷贝到 deepObj 对象中,包括 obj 里面的子对象。 3. 浅拷贝实现

(1)遍历赋值实现

    var shallowCopy = function(obj) {
        // 只拷贝对象
        if (typeof obj !== 'object') return;
        // 根据obj的类型判断是新建一个数组还是对象
        var newObj = obj instanceof Array ? [] : {};
        // 遍历obj,并且判断是obj的属性才拷贝
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                newObj[key] = obj[key];
            }
        }
        return newObj;
    }
复制代码

(2)ES6扩展运算符

var obj = { a:1, arr: [2,3] };
var obj1 = {...obj}
复制代码

(3)数组方法(只适用于类数组对象)

Array.from(arrayLike)

从类数组对象或者可迭代对象中创建一个新的数组实例。返回一个新的数组。

var array1 = ['a', ['b', 'c'], 'd'];
var array2 = Array.from(array1);
array1[1][0] = 'e';

console.log(array2[1][0]);//输出结果为: "e"
复制代码

Array.prototype.concat()

用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

var array1 = ['a', ['b', 'c'], 'd'];
var array2 = array1.concat();
array1[1][0] = 'e';

console.log(array2[1][0]);//输出结果为: "e"
复制代码

Array.prototype.slice()

返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。

var array1 = ['a', ['b', 'c'], 'd'];
var array2 = array1.slice();
array1[1][0] = 'e';

console.log(array2[1][0]);//输出结果为: "e"
复制代码

(4)ES6方法Object.assign()

用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

var obj = { a:1, arr: [2,3] };
var obj1 = Object.assign({}, obj);
obj.arr[1] = 5;

console.log(obj1.arr[1]);//输出结果:5。
复制代码
  1. 深拷贝实现

(1)借助JSON全局对象,然而使用这种方法会有一些隐藏的坑,它能正确处理的对象只有NumberStringBooleanArray,扁平对象,即那些能够被json直接表示的数据结构。

var obj = { a:1, arr: [2,3] };
var obj1 = JSON.parse(JSON.stringify(obj));
obj.arr[1] = 5;

console.log(deepObj.arr[1]); //输出结果:3。
复制代码

局限性:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
var arr = [function(){
    console.log(a)
}, {
    b: function(){
        console.log(b)
    }
}]

var new_arr = JSON.parse(JSON.stringify(arr));
console.log(new_arr);
复制代码

执行结果:

(2) 浅拷贝+递归: 当遇到子对象是引用对象时,采用递归层层复制。

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

var obj = { a:1, arr: [2,3] };
var obj1 = deepCopy(obj);
obj.arr[1] = 5;

console.log(obj.arr[1]); //输出结果:3。
复制代码

存在问题:

  • 没有对传入参数进行校验,传入 null 时应该返回 null 而不是 {}
let newObj = deepCopy({value:null});
console.log(newObj);//输出结果为:{value:{}}
复制代码
  • 对于对象的判断逻辑不严谨,因为 typeof null === 'object'
  • 没有考虑数组的兼容 (3)jQuery版的extend

推荐两篇文章吧:

  • 面试题之如何实现一个深拷贝
  • JavaScript专题之从零实现jQuery的extend
  1. 各语言深拷贝比较

(1)jQuery —— $.clone() / $.extend()

(2) Underscore —— _.clone()

在 Underscore 中有这样一个方法:_.clone(),这个方法实际上是一种浅复制 (shallow-copy),所有嵌套的对象和数组都是直接复制引用而并没有进行深复制。来看一下例子应该会更加直观:

(3)lodash —— _.clone() / _.cloneDeep()

在lodash中关于复制的方法有两个,分别是_.clone()_.cloneDeep()。其中_.clone(obj, true)等价于_.cloneDeep(obj)。更厉害的是,lodash 针对存在环的对象的处理也是非常出色的。因此相较而言,lodash 在深复制上的行为反馈比前两个库好很多,是更拥抱未来的一个第三方库。

参考文章:

  • js 深拷贝 vs 浅拷贝
  • javascript中的深拷贝和浅拷贝?

你可能感兴趣的:(JS深浅拷贝实现)