赋值、深拷贝、浅拷贝区别

赋值

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

  • 基本数据类型:赋值后两个变量的值互相没有关联关系,改变其一不会引起另一个变量值变化。
1
2
3
4
5
6
let a = 'abc'
let b = a
console.log(b) // 'abc'
a = 'def'
console.log(a) // 'def'
console.log(b) // 'abc'

基本数据类型赋值时会在栈内存中创建一个新值。

  • 引用数据类型:普通赋值操作使两个变量具有相同引用,指向同一个对象,改变任何一个,另一个取到的都会是改变后的值。
1
2
3
4
5
6
7
8
9
let a = {
name: 'abc',
age: 20
}
let b = a
console.log(b) // {name: "abc", age: 20}
a.name = 'def'
console.log(a) // {name: "def", age: 20}
console.log(b) // {name: "def", age: 20}

引用类型赋值时会在栈内存中存储引用对象的指针,真实对象在堆内存中。

想要得到两个相同值的引用类型的数据,又不想让它们指向同一个对象就需要用到深拷贝。

浅拷贝

浅拷贝只是复制一层对象,当对象的属性是引用类型时,实际复制的是该属性的引用地址,当引用地址对应的值改变时,其他值为该引用地址的变量都会改变。

浅拷贝-直接赋值

该方法只能拷贝基本数据类型的值,引用类型的值第一层属性就会变成拷贝引用地址

示例见赋值操作

浅拷贝-Object.assign()

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

它会拷贝浅层引用类型对象的属性,只会拷贝对象的第一层属性,对象内的引用类型属性只会拷贝引用地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = {
name: 'abc',
shop: {
price: 100
}
}

let b = Object.assign({}, a)
console.log(b) // {name: "abc", shop: {price: 100}}
// 当a的值改变时
a.name = 'def' // 第一层属性
a.shop.price = 200 // 第二层属性
console.log(a) // {name: "def", shop: {price: 200}}
console.log(b) // {name: "abc", shop: {price: 200}}

当改变a的第二层属性时,b的属性值也改变了

浅拷贝-‘‘扩展运算符

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
let a = {
name: 'abc',
shop: {
price: 100
}
}

let b = {...a}
console.log(b) // {name: "abc", shop: {price: 100}}
a.name = 'def'
a.shop.price = 200
console.log(a) // {name: "def", shop: {price: 200}}
console.log(b) // {name: "abc", shop: {price: 200}}

同Object.assign()一样,当改变a的第二层属性时,b的属性值也改变了

浅拷贝-Array.slice()方法

slice(start [,end])方法不会改变原数组,会返回一个新的数组,包含从 start 到 end (不包括该元素)的元素。

1
2
3
4
5
6
7
let a = [0, '1', [2, 3, 4]]
let b = a.slice(0)
console.log(b) // [0, "1", [2, 3, 4]]
a[0] = 123
a[2][0] = 234
console.log(a) // [123, "1", [234, 3, 4]]
console.log(b) // [0, "1", [234, 3, 4]]

这个方法只能浅拷贝数组使用

深拷贝

深拷贝会拷贝对象的所有属性,相当于创建一个新的对象,深拷贝的对象与原对象除了值相同外不存在互相的引用关系,当改变一个对象属性时另一个不会发生变化,深拷贝相对于浅拷贝开销较大,拷贝前后对象互不影响。

深拷贝-JSON.parse() JSON.stringify()

JSON.parse(JSON.stringify(Object)),原理是将引用类型对象转换成JSON字符串,再将字符串转换为一个对象,这时返回的对象会成为一个新的引用类型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let a = {
name: 'abc',
shop: {
price: 100,
title: undefined,
avatar: null,
read: function(){},
symbol: Symbol('bcd')
}
}

let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "abc", shop: {price: 100, avatar: null}}
// 当a的值改变时
a.name = 'def' // 第一层属性
a.shop.price = 200 // 第二层属性
console.log(a) // {name: "def", shop: {price: 200, title: undefined, avatar: null, read: ƒ (), symbol: Symbol(bcd)}}
console.log(b) // {name: "abc", shop: {price: 100, avatar: null}}

这个方法拷贝对象时,会有以下问题:

  • 会忽略 undefined
  • 会忽略 symbol
  • 会忽略函数
  • 不能解决循环引用的对象(会报错Uncaught TypeError: Converting circular structure to JSON)
  • 不能正确处理new Date()(会将时间对象转换为字符串时间)
  • 不能处理正则

深拷贝-递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function deepCopy( source ) {
if (typeof source !== 'object') return source; //如果不是对象的话直接返回
let target = Array.isArray( source ) ? [] : {} //数组兼容
for ( var k in source ) {
if (source.hasOwnProperty(k)) {
if ( typeof source[ k ] === 'object' ) {
target[ k ] = deepCopy( source[ k ] )
} else {
target[ k ] = source[ k ]
}
}
}
return target
}

深拷贝-获取所有属性逐个添加

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
function copyObject(orig) {
// Object.create(prototype) 使用原型创建新的对象
// Object.getPrototypeOf(obj) 返回对象的原型
var copy = Object.create(Object.getPrototypeOf(orig));
copyOwnPropertiesFrom(copy, orig);
return copy;
}


function copyOwnPropertiesFrom(target, source) {
// Object.getOwnPropertyNames 返回指定对象的所有属性名组成的数组(包含不可枚举属性)
// Object.getOwnPropertyDescriptor 返回对象上属性对应的属性描述符
// Object.defineProperty 直接在对象上定义一个新属性,或者修改一个现有属性
Object
.getOwnPropertyNames(source)
.forEach(function (propKey) {
var desc = Object.getOwnPropertyDescriptor(source, propKey);
if (typeof desc.value === 'object') {
target[propKey] = copyObject(source[propKey]);
} else {
Object.defineProperty(target, propKey, desc);
}
});
return target;
}

后两个方法原文地址: https://www.cnblogs.com/dobeco/p/11295316.html
方法参考链接:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty