数据类型 | 深浅拷贝

数据类型

  • 基本数据类型:简单的数据,保存基本数据类型的变量是按进行访问的,因此直接保存在栈内存中
  • 引用数据类型:保存在堆内存中,在栈内存中保存了一个堆内存中实际对象的引用,引用数据类型的变量是按照引用进行访问的

复制值

  • 基本数据类型:在通过变量把一个基本数据赋值到另一个变量时,基本数据会被复制到新变量的位置
1
2
let num1 = 5;
let num2 = num1;

从上图可知,num1和num2是独立使用的,互不干扰

  • 引用数据类型:在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象,操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来

深浅拷贝

深浅拷贝都是针对于引用类型的,因为引用类型是存放在堆内存中的,在栈地址中有一个或者多个地址来指向堆内存的某一数据

浅拷贝

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝

如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

浅拷贝是指被复制对象的所有变量都与原来的对象含有相同的值,而所有的对其他对象的引用仍然指向原来的对象

浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象,如果你修改了“副本”的值,那么原来的对象也会被修改

浅拷贝只拷贝一层,深层次的引用类型则共享内存地址

深拷贝

将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

深拷贝是一个整个独立对象的拷贝,深拷贝会拷贝所有的属性,并且拷贝属性指向的动态分配的内存

深拷贝把要复制的对象所引用的对象都复制了一遍,如果你修改了“副本”的值,那么原来的对象不会被修改,两者是相互独立的

实现浅拷贝的方式

  • Object.assign
  • Array.protptype.slice
  • Array.protptype.concat
  • 使用拓展运算符
  • 手写浅拷贝

Object.assign

补课:Object.assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = Object.assign({}, person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play tennies']

Array.prototype.slice()

补课:slice

1
2
3
4
5
6
7
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = colors.slice(0);

// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]

Array.prototype.concat()

补课:concat

1
2
3
4
5
6
7
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = colors.concat();

// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]

拓展运算符

1
2
3
4
5
6
7
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = [...colors];

// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]

手写浅拷贝

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
function shallowClone(target) {
let res, targetType = Object.prototype.toString.call(target).slice(8,-1).toLowerCase();

if (target === 'object') {
res = {};
} else if (target === 'array') {
res = [];
} else {
return target;
}

for (let key in target) {
res[key] = target[key];
};

return res;
};

// test1
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = shallowClone(colors);
// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]


// test2
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = shallowClone(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play tennies']

实现深拷贝的方式

  • _.cloneDeep()
  • JQuery.extend()
  • JSON.stringify()
  • 手写深拷贝

_.cloneDeep()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const _ = require('loash');
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};
let person1 = _.cloneDeep(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

JQuery.extend()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const $ = require('jquery');
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};
let person1 = $.extend(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

JSON.stringify()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = JSON.parse(JSON.stringify(person));

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

// 打印person1发现...
console.log(person1);
console.log(person);

// 如下图,function不见了...这就是JSON.stringify()方法的弊端

这种方法会存在弊端:会忽略undefinedfunctionsymbol的存在

1
2
3
4
5
6
7
8
9
10
11
12
const test = {
a: 'hello',
b: undefined,
c: Symbol('I am fine'),
d: function() {
console.log('and you?');
},
};

const test1 = JSON.parse(JSON.stringify(test));

console.log(test1); // {a: 'hello'}

手写深拷贝

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
function deepClone(target) {
let res, targetType = Object.prototype.toString.call(target).slice(8, -1).toLowerCase();

if (targetType === 'object') {
res = {};
} else if (targetType === 'array') {
res = [];
} else {
return target;
};


for (let key in target) {
res[key] = deepClone(target[key]);
};

return res;
};

// test1
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = deepClone(colors);
// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'black']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]


// test2
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = deepClone(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

拓展:赋值和拷贝

拷贝区别于赋值最重要的一点是:拷贝会在堆上创建一个新的对象

示意图如下图所示:

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

参考

深入理解JS深浅拷贝

赋值和拷贝