1.数据类型

1.数据的基本类型分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和引用数据类型(Function,Array,Object)

2.基本数据类型的特点:直接存储在栈(stack)中的数据;引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

2.浅拷贝和深拷贝的概念

1.深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型

2.概念:

浅拷贝 :只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用同一个堆内存地址

深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象

3.赋值和浅拷贝

1.赋值:

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈内存中的地址,而不是堆内存中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变都是同一个堆内存空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义一个obj1对象
var obj1 = {
'name' : 'hello',
'age' : '20',
'language' : ['a',['b','c'],[1,2]],
};
//把obj1赋值给obj2
var obj2 = obj1;
//修改obj2的数据
obj2.name = "world";
obj2.language[1] = ["你","好"];
//打印输出
console.log('obj1:',obj1)
console.log('obj2:',obj2)

以上,可以看出当你把obj1赋值给obj2后,因为赋值后的对象obj2赋值的是原对象obj1的栈内存地址,在对obj2的数据进行修改后,原数据obj1的值也进行了改变。

2.浅拷贝:

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

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
// 定义一个obj1对象
var obj1 = {
'name' : 'hello',
'age' : '20',
'language' : ['a','b','c'],
};
// 把obj1浅拷贝为obj3
var obj3 = shallowCopy(obj1)
function shallowCopy (src){
var newObj = {};
for(var prop in src ){
console.log(prop)
//hasOwnProperty(propertyName)方法 是用来检测属性是否为对象的自有属性,如果是,返回true,否者false;
//参数propertyName指要检测的属性名;
if(src.hasOwnProperty(prop)){
newObj[prop] = src[prop]
}
}
return newObj
}
obj3.name = 'world'
obj3.language[0] = 'ddd'

console.log("obj1", obj1);
console.log("obj3", obj3);

以上,可以看出在进行浅拷贝之后,对obj3基本类型的值name进行修改,而原对象obj1的值name并没有改变,而对obj3的引用类型language进行修改后,原对象obj1的值language发生了改变。

所以,浅拷贝是对原对象的属性值进行精准复制,如果原对象的属性值是基本类型,那就是值的引用,所以浅拷贝后修改基本类型不会修改到原对象的,如果原对象属性值是引用类型,那么就是对引用类型属性值的栈内存的复制,所以修改引用类型属性值的时候会修改到原对象。

4.浅拷贝的方式

1.Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj1 = {
name: 'hello',
age: 20,
hobby: {
ball: 'a'
}
}
let obj2 = Object.assign({},obj1)
obj2.name = 'world'
obj2.hobby.ball = 'b'
console.log('obj1', obj1.name) //hello
console.log('obj2', obj2.name) //world
console.log('obj1', obj1.hobby.ball) //b
console.log('obj2', obj2.hobby.ball) //b

注意:当object只有一层的时候,是深拷贝

1
2
3
4
5
6
let obj = {
name: 'hello'
};
let obj2 = Object.assign({},obj);
obj2.username = 'world';
console.log(obj);//name:hello

2.Array.prototype.slice()

1
2
3
4
5
6
let arr = [1, 2, {
name: ' hello'
}];
let arr3 = arr.slice();
arr3[2].name = 'world'
console.log(arr);//name:world

3.Array.prototype.concat()

1
2
3
4
5
6
let arr = [1, 2, {
name: 'hello'
}];
let arr2=arr.concat();
arr2[2].name = 'world';
console.log(arr);//name:world

5.深拷贝的方式

1.JSON.parse(JSON.stringify())

1
2
3
4
5
6
7
8
var arr = [1, 2,{
name: 'hello'
}];
let arr1 = JSON.parse(JSON.stringify(arr))
arr1[2].name = 'world'
arr1[0] = 5
console.log(arr[2].name) //hello
console.log(arr[0]) //1

原理就是用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈内存,

缺点:当对象里面有函数的话,深拷贝后,函数会消失

2.手写递归函数实现深拷贝

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
var obj = { //原数据,包含字符串、对象、函数、数组等不同的类型
name: "hello",
main: {
a: 1,
b: 2
},
fn: function() {},
friends: [1, 2, 3, [4, 5]]
}
function copy(obj) {
//声明一个变量用来储存拷贝之后的内容
let newobj = null;
//判断数据类型是否是复杂类型,如果是则调用自己,再次循环,如果不是,直接赋值即可,
//由于null不可以循环但类型又是object,所以这个需要对null进行判断
if (typeof(obj) == 'object' && obj !== null) {
//声明一个变量用以储存拷贝出来的值,根据参数的具体数据类型声明不同的类型来储存
newobj = obj instanceof Array ? [] : {};
//循环obj 中的每一项,如果里面还有复杂数据类型,则直接利用递归再次调用copy函数
for (var i in obj) {
newobj[i] = copy(obj[i])
}
} else {
newobj = obj
}
return newobj; //函数必须有返回值,否则结构为undefined
}
var obj2 = copy(obj)
obj2.name = 'world'
obj2.main.a = 100
console.log(obj)
console.log(obj2)

递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝