浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

释放双眼,带上耳机,听听看~!

文章首发于sau交流学习社区

一、赋值(Copy)

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

1、基本数据类型:赋值,赋值之后两个变量互不影响

2、引用数据类型:赋**址**,两个变量具有相同的引用,指向同一个对象,相互之间有影响

对基本类型进行赋值操作,两个变量互不影响。

// saucxs
let a = \"saucxs\";
let b = a;
console.log(b);  // saucxs

a = \"change\";
console.log(a);   // change

console.log(b);    // saucxs

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

对引用类型进行赋**址**操作,两个变量指向同一个对象,改变变量 a 之后会影响变量 b,哪怕改变的只是对象 a 中的基本类型数据。

// saucxs
let a = {
    name: \"saucxs\",
    book: {
        title: \"You Don\'t Know JS\",
        price: \"45\"
    }
}
let b = a;
console.log(b);
// {
// 	name: \"saucxs\",
// 	book: {title: \"You Don\'t Know JS\", price: \"45\"}
// } 

a.name = \"change\";
a.book.price = \"55\";
console.log(a);
// {
// 	name: \"change\",
// 	book: {title: \"You Don\'t Know JS\", price: \"55\"}
// } 

console.log(b);
// {
// 	name: \"change\",
// 	book: {title: \"You Don\'t Know JS\", price: \"55\"}
// }

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

通常在开发中并不希望改变变量 a 之后会影响到变量 b,这时就需要用到浅拷贝和深拷贝。

 

二、浅拷贝(Shallow Copy)

1、什么是浅拷贝

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

 

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

 

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

简单来说可以理解为浅拷贝只解决了第一层的问题,拷贝第一层的**基本类型值**,以及第一层的**引用类型地址**。

 

2、浅拷贝使用场景

2.1 Object.assign()

`Object.assign()` 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

有些文章说`Object.assign()` 是深拷贝,其实这是不正确的。

// saucxs
let a = {
    name: \"saucxs\",
    book: {
        title: \"You Don\'t Know JS\",
        price: \"45\"
    }
}
let b = Object.assign({}, a);
console.log(b);
// {
// 	name: \"saucxs\",
// 	book: {title: \"You Don\'t Know JS\", price: \"45\"}
// } 

a.name = \"change\";
a.book.price = \"55\";
console.log(a);
// {
// 	name: \"change\",
// 	book: {title: \"You Don\'t Know JS\", price: \"55\"}
// } 

console.log(b);
// {
// 	name: \"saucxs\",
// 	book: {title: \"You Don\'t Know JS\", price: \"55\"}
// }

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

上面代码改变对象 a 之后,对象 b 的基本属性保持不变。但是当改变对象 a 中的对象 `book` 时,对象 b 相应的位置也发生了变化。

 

2.2 展开语法 `Spread`

// saucxs
let a = {
    name: \"saucxs\",
    book: {
        title: \"You Don\'t Know JS\",
        price: \"45\"
    }
}
let b = {...a};
console.log(b);
// {
// 	name: \"saucxs\",
// 	book: {title: \"You Don\'t Know JS\", price: \"45\"}
// } 

a.name = \"change\";
a.book.price = \"55\";
console.log(a);
// {
// 	name: \"change\",
// 	book: {title: \"You Don\'t Know JS\", price: \"55\"}
// } 

console.log(b);
// {
// 	name: \"saucxs\",
// 	book: {title: \"You Don\'t Know JS\", price: \"55\"}
// }

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

 

2.3 Array.prototype.slice方法

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

// saucxs
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]]

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

可以看出,改变 `a[1]` 之后 `b[0]` 的值并没有发生变化,但改变 `a[2][0]` 之后,相应的 `b[1][0]` 的值也发生变化。

说明 `slice()` 方法是浅拷贝,相应的还有`concat`等,在工作中面对复杂数组结构要额外注意。

 

三、深拷贝(Deep Copy)

3.1 什么是深拷贝?

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

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

 

3.2 使用深拷贝的场景

3.2.1 JSON.parse(JSON.stringify(object))

// saucxs
let a = {
    name: \"saucxs\",
    book: {
        title: \"You Don\'t Know JS\",
        price: \"45\"
    }
}
let b = JSON.parse(JSON.stringify(a));
console.log(b);
// {
// 	name: \"saucxs\",
// 	book: {title: \"You Don\'t Know JS\", price: \"45\"}
// } 

a.name = \"change\";
a.book.price = \"55\";
console.log(a);
// {
// 	name: \"change\",
// 	book: {title: \"You Don\'t Know JS\", price: \"55\"}
// } 

console.log(b);
// {
// 	name: \"saucxs\",
// 	book: {title: \"You Don\'t Know JS\", price: \"45\"}
// }

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

完全改变变量 a 之后对 b 没有任何影响,这就是深拷贝的魔力。

我们看下对数组深拷贝效果如何。

// saucxs
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]]

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

对数组深拷贝之后,改变原数组不会影响到拷贝之后的数组。

但是该方法有以下几个问题:

(1)会忽略 `undefined`

(2)会忽略 `symbol`

(3)不能序列化函数

(4)不能解决循环引用的对象

(5)不能正确处理`new Date()`

(6)不能处理正则

 

其中(1)(2)(3) `undefined`、`symbol` 和函数这三种情况,会直接忽略。

// saucxs
let obj = {
    name: \'saucxs\',
    a: undefined,
    b: Symbol(\'saucxs\'),
    c: function() {}
}
console.log(obj);
// {
// 	name: \"saucxs\", 
// 	a: undefined, 
//  b: Symbol(saucxs), 
//  c: ƒ ()
// }

let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: \"saucxs\"}

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

 

其中(4)循环引用会报错

// saucxs
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

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

 

其中(5)* `new Date` 情况下,转换结果不正确。

// saucxs
new Date();
// Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time)

JSON.stringify(new Date());
// \"\"2018-12-24T02:59:25.776Z\"\"

JSON.parse(JSON.stringify(new Date()));
// \"2018-12-24T02:59:41.523Z\"

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

解决方法转成字符串或者时间戳就好了。

// saucxs
let date = (new Date()).valueOf();
// 1545620645915

JSON.stringify(date);
// \"1545620673267\"

JSON.parse(JSON.stringify(date));
// 1545620658688

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

 

其中(6)正则情况下

// saucxs
let obj = {
    name: \"saucxs\",
    a: /\'123\'/
}
console.log(obj);
// {name: \"saucxs\", a: /\'123\'/}

let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: \"saucxs\", a: {}}

浅析JavaScript解析赋值、浅拷贝和深拷贝的区别

 

PS:为什么会存在这些问题可以学习一下 JSON。

除了上面介绍的深拷贝方法,

常用的还有`jQuery.extend()` 和 `lodash.cloneDeep()`,后面文章会详细介绍源码实现。

 

四、总结

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

 

五、参考

1、深拷贝和浅拷贝

2、MDN之Object.assign()

3、MDN之展开语法

4、MDN之Array.stypetype.slice()

给TA打赏
共{{data.count}}人
人已打赏
随笔日记

再谈领域驱动设计

2020-11-9 4:39:07

随笔日记

[日志分析] Access Log 日志分析

2020-11-9 4:39:09

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索