什么是深浅拷贝

  • 浅拷贝与深拷贝主要是针对保存在堆内存里的复杂数据类型所给出的名词,都是进行复制。

  • 主要区别就是:复制的是引用(地址)还是实例。复制出来的新对象和原来的对象是否会互相影响。

  • 浅拷贝指的是将复杂数据类型在中保存的地址复制一份,所指向的数据是同一份。修改原值会影响新值。

  • 深拷贝是指将复杂数据类型完整的复制一份,拷贝对象的属性并重新创建一个对象,不会影响原始值。

  • 注意,function,regexp,Date不可复制。

对象的深浅拷贝实现

  1. Object.assign({},originObject)
let a = {
    age:1,
    person:{
        name:'xinxin'
    }
}

// 第一层不影响
let b = Object.assign({},a);
console.log(b.age);// 1
b.age = 2;
console.log(a.age) // 1

// 第二层互相影响
b.person.name = 'xin';
console.log(a.person.name) // 'xin'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

2.解构{ ...originObject }

let a = {
    age:1,
    person:{
        name:'xinxin'
    }
}
// 用法
let b = { ...a }

// 效果同第一种一样,第一层不互相影响,第二层开始相互影响
1
2
3
4
5
6
7
8
9
10
  1. lodash(_.clone());

拷贝规则同Object.assign;

let obj2 = _.clone(obj1);

数组的深浅拷贝实现

  1. Array.prototype.concat()
// concat这么用是深拷贝
let arr1 = [0,1,[2,[3,4]]];
let arr2 = [].concat(arr1);

arr2[0]='change';
console.log(arr1[0]);// 0

arr2[2][0]='change change';
console.log(arr1[2]);// [3,4]

1
2
3
4
5
6
7
8
9
10
// concat这么用是浅拷贝
let arr11 = [0,1,[2,[3,4]]];
let arr22 = arr11.concat();

// 第一层不影响
arr22[0]='change';
console.log(arr11[0]);// 0

// 第二层影响
arr22[2][0]='change change';
console.log(arr11[2]);// 'change change'
1
2
3
4
5
6
7
8
9
10
11
// 数组里面是对象的话就是浅拷贝
var arr1 = [{"name":"Roubin"},{"name":"RouSe"}];//原数组
var arr2 = [].concat(arr1);//拷贝数组
arr1[1].name="Tom";
console.log(arr1); // [{"name":"Roubin"},{"name":"Tom"}]
console.log(arr2);// [{"name":"Roubin"},{"name":"Tom"}]
1
2
3
4
5
6
  1. Array.prototype.slice();
var a1=[["1","2","3"],"2","3"];
var a2=a1.slice(0);

// 第一层不影响
a1[1] = 1;
console.log(a2[1]);// 2

// 第二层影响
a1[0][0]=0; //改变a1第一个元素中的第一个元素
console.log(a1);  //[["0","2","3"],"2","3"]
console.log(a2);   //[["0","2","3"],"2","3"]
1
2
3
4
5
6
7
8
9
10
11
  1. ES6扩展运算符
var arr = [1,2,[3,4,5]]
var arr2 = [...arr]
arr[0] = 5

// 第一层不影响
console.log(arr2[0])  //1

// 第二层影响
arr[2][0] = 0
console.log(arr[2][0]) //0

1
2
3
4
5
6
7
8
9
10
11

深拷贝实现

  1. JSON.parse(JSON.stringify());
let obj2=JSON.parse(JSON.stringify(obj1));
1

缺点:

  • 不能拷贝函数;
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时) 不可枚举的属性会被忽略
  • 如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如:
var obj = {
  foo: 'foo',
  toJSON: function () {
    return 'bar';
  }
};
JSON.stringify(obj);      // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'
1
2
3
4
5
6
7
8
  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
let obj = {
    name: 'Yvette',
    age: 18,
    hobbies: ['reading', 'photography'],
    sayHi: function() {
        console.log(sayHi);
    }
}
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);//{ name: 'Yvette', age: 18, hobbies: [ 'reading', 'photography' ] }
1
2
3
4
5
6
7
8
9
10
  • 原型链上的属性无法获取
function Super() {

}
Super.prototype.location = 'NanJing';
function Child(name, age, hobbies) {
    this.name = name;
    this.age = age;
}
Child.prototype = new Super();

let obj = new Child('Yvette', 18);
console.log(obj.location); //NanJing
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);//{ name: 'Yvette', age: 18}
console.log(newObj.location);//undefined;原型链上的属性无法获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 不能解决循环引用的对象
  • 不能正确的处理 new Date()
  • 不能处理正则
  1. 递归赋值
function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            //判断是否为自身属性
            if(obj.hasOwnProperty(key)){
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

还有这样写的,不懂这个new obj.constructor();秒在哪里

function deepCloneX(obj) { //递归拷贝
        if (obj instanceof RegExp) return new RegExp(obj);
        if (obj instanceof Date) return new Date(obj);
        // 如果是个基本数据类型  或者递归的时候如果是个 function的时候 也直接返回一个函数
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }
        let newObj = new obj.constructor();
        for (let key in obj) {
            //如果 obj[key] 是复杂数据类型,递归
            if (obj.hasOwnProperty(key)) {//是否是自身的属性
                newObj[key] = deepCloneX(obj[key]);
            }
        }
        return newObj;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. lodash(_.cloneDeep());
let obj2 = _.cloneDeep(obj1);
1
  1. 使用MessageChannel 信道通信api (逼格高,兼容性还不错)
function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}
var obj = {a: 1, b: {
    c: b
}}
// 可以处理 undefined 和互相引用对象
(async () => {
  const clone = await structuralClone(obj)
})()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 第三方库jquery的JQ.extend、underscore

小知识

Object.prototype.toString.call({name:1}).slice(8,-1) // "Object"

Object.prototype.toString.call([1]).slice(8,-1) // "Array"
1
2
3