赋值、浅拷贝和深拷贝

赋值

赋值是将某一数值或对象赋给某个变量的过程,分为两类:

  • 基本数据类型:赋值,赋值之后两个变量互不影响。
  • 引用数据类型:赋址,两个变量具有相同的引用,指向同一个对象,相互之间有影响。 来看下面两个例子:
    先看基本数据类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let a = "java";
    let b = a;
    console.log(b);
    //java
    a = "javascript";
    console.log(a);
    // javascript
    console.log(b);
    // java
    上面代码的输出为:
    1
    2
    3
    java
    javascript
    java
    如果是引用数据类型呢?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let a = {
    name: "javascript",
    book: {
    title: "I love javascript",
    price: 45
    }
    }
    let b = a;
    console.log(b);
    上面代码的输出为:
    1
    2
    3
    4
    5
    6
    7
    {
    name: "javascript",
    book: {
    title: "I love javascript",
    price: 45
    }
    }
    如果这时候改变a的值,像下面这样:
    1
    2
    3
    4
    5
    a.name = "java";
    a.book.title = "I love java";
    a.book.price = 55;
    console.log(a);
    console.log(b);
    输出的结果为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    name: "java",
    book: {
    title: "I love java",
    price: 55
    }
    }
    {
    name: "java",
    book: {
    title: "I love java",
    price: 55
    }
    }
    可以看到a和b的值都改变了,但是通常在开发中我们并不希望改变变量 a 之后会影响到变量 b,这时就需要用到浅拷贝和深拷贝。

浅拷贝

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
alt

上图中,SourceObj 是原对象,其中包含基本类型属性 field1 和引用类型属性 refObj。浅拷贝之后基本类型数据 field2 和 filed1 是不同属性,互不影响。但引用类型 refObj 仍然是同一个,改变之后会对另一个对象产生影响。

那么浅拷贝有哪些应用场景呢?

  • Object.assign()
    Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,它将返回目标对象,来看下面的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    let a = {
    name: "java",
    book: {
    title: "I love java",
    price: 45
    }
    }
    let b = Object.assign({}, a);
    console.log(b);
    // {
    // name: "java",
    // book: {
    // title: "I love java",
    // price: 45
    // }
    // }
    a.name = "javascript";
    a.book.title = "I love javascript";
    a.book.price = 55;
    console.log(a);
    // {
    // name: "javascript",
    // book: {
    // title: "I love javascript",
    // price: 55
    // }
    // }
    console.log(b);
    // {
    // name: "java",
    // book: {
    // title: "I love javascript",
    // price: 55
    // }
    // }

    上面代码改变对象 a 之后,对象 b 的基本属性保持不变。但是当改变对象 a 中的对象 book 时,对象 b 也跟着发生了变化,因为它们指向的是同一块内存地址。

  • 展开语法Spread

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    let a = {
    name: "java",
    book: {
    title: "I love java",
    price: 45
    }
    }
    let b = {...a};
    console.log(b);
    // {
    // name: "java",
    // book: {
    // title: "I love java",
    // price: 45
    // }
    // }
    a.name = "javascript";
    a.book.title = "I love javascript";
    a.book.price = 55;
    console.log(a);
    // {
    // name: "javascript",
    // book: {
    // title: "I love javascript",
    // price: 55
    // }
    // }
    console.log(b);
    // {
    // name: "java",
    // book: {
    // title: "I love javascript",
    // price: 55
    // }
    // }

    通过代码可以看出实际效果和 Object.assign() 是一样的。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let a = [0, "1", [2, 3]];
    let b = a.slice(1);
    console.log(b);
    // ["1", [2, 3]]
    a[1] = "99";
    a[2][0] = 4;
    console.log(a);
    // [0, "99", [4, 3]]
    console.log(b);
    // ["1", [4, 3]]

    通过,改变 a[1] 之后 b[0] 的值并没有发生变化,但改变 a[2][0] 之后,相应的 b[1][0] 的值也发生变化。说明 slice() 方法是浅拷贝,相应的还有concat等,在实际工作中使用数组对象等引用数据类型时要特别注意。

  • jQuery.extend()

使用jQuery语法前记得先引入jQuery,来看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
let a = {
name: "java",
book: {
title: "I love java",
price: 45
}
};
let b = {};
$.extend(b, a); //jQuery.extend(b, a);
console.log(a);
// {
// name: "java",
// book : {
// title: "I love java",
// price: 45
// }
// }
console.log(b);
// {
// name: "java",
// book : {
// title: "I love java",
// price: 45
// }
// }
a.name = "javascript";
a.book.title = "I love javascript";
a.book.price = 55;
console.log(a);
// {
// name: "javascript",
// book : {
// title: "I love javascript",
// price: 55
// }
// }
console.log(b);
// {
// name: "java",
// book : {
// title: "I love javascript",
// price: 55
// }
// }

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。

alt

那么深拷贝有什么使用场景呢?

  • JSON.parse(JSON.stringify(object))
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    let a = {
    name: "java",
    book: {
    title: "I love java",
    price: 45
    }
    }
    let b = JSON.parse(JSON.stringify(a));
    console.log(b);
    // {
    // name: "java",
    // book: {
    // title: "I love java",
    // price: 45
    // }
    // }
    a.name = "javascript";
    a.book.title = "I love javascript";
    a.book.price = 55;
    console.log(a);
    // {
    // name: "javascript",
    // book: {
    // title: "I love javascript",
    // price: 55
    // }
    // }
    console.log(b);
    // {
    // name: "java",
    // book: {
    // title: "I love java",
    // price: 45
    // }
    // }
    完全改变变量 a 之后对 b 没有任何影响,这就是深拷贝的魔力。那么对数组深拷贝效果如何?来看下面的例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let a = [0, "1", [2, 3]];
    let b = JSON.parse(JSON.stringify( a.slice(1) ));
    console.log(b);
    // ["1", [2, 3]]
    a[1] = "99";
    a[2][0] = 4;
    console.log(a);
    // [0, "99", [4, 3]]
    console.log(b);
    // ["1", [2, 3]]
    可以发现,对数组深拷贝之后,改变原数组不会影响到拷贝之后的数组。但是这种方法有如下的几个问题:
  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
  • 不能正确处理new Date()
  • 不能处理正则

undefined、symbol 和函数这三种情况,会直接忽略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let obj = {
name: "javascript",
a: undefined,
b: Symbol("javascript"),
c: function() {}
}
console.log(obj);
// {
// name: "javascript",
// a: undefined,
// b: Symbol(javascript),
// c: ƒ ()
// }
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// {
// name: "javascript"
// }

循环引用情况下,会报错:

1
2
3
4
5
6
7
8
9
10
11
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

new Date() 情况下,转换结果不正确:

1
2
3
4
5
6
new Date();
// Sat Sep 19 2020 15:29:37 GMT+0800 (中国标准时间)
JSON.stringify(new Date());
// ""2020-09-19T07:29:50.597Z""
JSON.parse(JSON.stringify(new Date()));
// "2020-09-19T07:30:14.480Z"

将 new Date() 的结果转换为字符串或时间戳就ok了:

1
2
3
4
5
6
7
let date = (new Date()).valueOf();
console.log(date);
// 1600500782999
JSON.stringify(date);
// "1600500782999"
JSON.parse(JSON.stringify(date));
// 1600500782999

正则情况下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
name: "javascript",
a: /'456'/
};
console.log(obj);
// {
// name: "javascript",
// a: /'456'/
// }
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {
// name: "javascript",
// a: {}
// }

总结

和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变

到这里,赋值、浅拷贝和深拷贝我们已经梳理完毕了,如果你有任何的问题,欢迎评论区留言,你的支持是我前进最大的动力。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2021 Sanmu
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信