1. 浅拷贝与深拷贝_js版
基础储备
1. 数据类型与数据结构
关于null和undefined:
有一个小故事:
1995年JavaScript诞生时,最初像Java一样,只设置了null作为表示”无”的值。根据C语言的传统,null被设计成可以自动转为0。但是,JavaScript的设计者,觉得这样做还不够,主要有以下两个原因:
null像在Java里一样,被当成一个对象。但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,作者觉得表示”无”的值最好不是对象。
JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。作者觉得,如果null自动转为0,很不容易发现错误。
因此,作者又设计了一个undefined。
先有null后有undefined,出来undefined是为了填补之前的坑。
null是一个表示”无”的对象(空对象指针),转为数值时为0;
undefined是一个表示”无”的原始值,转为数值时为NaN;
1 |
|
2. 关于修改栈与堆
图中,obj1和obj2都指向同一个object,修改obj2==
修改了引用地址==
修改了那个对象
3. 堆与栈比较
1 |
|
由上可知:
对象中传给变量的数据是引用类型的,会存储在堆中;
把对象中的属性/数组中的数组项赋值给变量,这时变量c是基本数据类型,存储在栈内存中;
改变栈中的数据不会影响堆中的数据;
3. 区别
1. 声明时内存分配不同
基本数据类型: 栈,便于迅速查询变量的值
引用类型: 堆,栈中存储的变量只是引用地址(指针)
引用类型的大小会改变,如果放在栈中,会降低其变量查询的速度。放在堆中,地址的大小是固定的,将地址存储在栈中对变量的性能没有任何负面影响
2. 不同的内存分配带来的不同访问机制
- 基本数据类型:直接访问
- 引用类型:通过地址去获得object的值
3. 复制变量时不同
基本数据类型: 复制之后完全独立,各自修改互不影响
引用类型: 复制的是引用地址
此时两者都指向同一个对象,修改会一起修改(复制并不会在堆内存中新生成一个一模一样的对象,只是复制多一个保存指向这个对象指针的变量)
复制的是引用地址
4. 参数传递不同
即把实参复制给形参的过程
正文
1. 介绍
浅拷贝
复制的标识堆内存中的数据,而是指向堆中的栈内存的指针,即复制的是引用地址
只复制一层对象的属性,不包括对象里面的引用类型的数据
只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存
深拷贝
递归复制了所有层级,程度深
复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变
2. 浅拷贝的实现
直接赋值
使用
object.assign()
1
Object.assign(target, ...sources)
1
2
3
4
5var obj = { a: {a: "hello", b: 21} };
var newObj = Object.assign({}, obj);
newObj.a.a = "changed";
console.log(obj.a.a); // "changed"但可以处理一层的深度拷贝
1
2
3
4
5var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1); // { a: 10, b: 20, c: 30 }
console.log(obj2); // { a: 10, b: 100, c: 30 }
3. 深拷贝的实现
手动复制
1
2
3
4
5var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1); // { a: 10, b: 20, c: 30 }
console.log(obj2); // { a: 10, b: 100, c: 30 }JSON做字符转换
使用
JSON.stringify()
把对象转成字符串,再用JSON.parse()
转换为新的对象1
2
3
4
5var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1); // { body: { a: 10 } }
console.log(obj2); // { body: { a: 20 } }缺点
抛弃对象的constructor => 深拷贝之后,会变成Object
故一般是Number,String,BOOlean,Array这些扁平对象去使用(可以被json直接表示的数据结构)
而RegExp、Function、Symbol等等不能转换成json的是无法通过这种方式进行深拷贝
递归拷贝
实现原理: 定义一个新的对象,遍历源对象的属性 并 赋给新对象的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function deepClone(Obj, newObj) {
var obj = newObj || {};
for (var i in Obj) {
var prop = Obj[i]; // 避免相互引用对象导致死循环,如Obj.a = Obj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {}; // array || string
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a); // {a: "hello", b: 21}使用Object.create()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function deepClone(Obj, newObj) { // 大致实现和递归差不多
var obj = newObj || {};
for (var i in Obj) {
var prop = Obj[i];
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}第三方函数
直接使用一些函数库来做到深拷贝
4. 特殊
对一维数组,可以实现深拷贝,例如:
扩展运算符
concat()
和slice()
1
2
3
4
5var arr = [1,2,3,4,5]
var [ ...arr2 ] = arr
arr[2] = 5
console.log(arr) //[1,2,5,4,5]
console.log(arr2) //[1,2,3,4,5]1
2
3
4
5var arr = ['a', 'b', 'c'];
var arrCopy = arr.concat();
arrCopy[0] = 'test'
console.log(arr); // ["a", "b", "c"]
console.log(arrCopy); // ["test", "b", "c"]1
2
3
4
5var arr = ['a', 'b', 'c'];
var arrCopy = arr.slice(0);
arrCopy[0] = 'test'
console.log(arr); // ["a", "b", "c"]
console.log(arrCopy); // ["test", "b", "c"]如果是多层的话,除了第一层其余的都只是进行浅拷贝
1
2
3
4
5
6
7
8
9
10
11const time = {
year: 2021,
month: 7,
day: { value: 1 },
};
const copyTime = { ...time };
copyTime.day.value = 2;
copyTime.month = 6;
console.log(copyTime); // { year: 2021, month: 6, day: { value: 2 } }
console.log(time); // { year: 2021, month: 7, day: { value: 2 } }
//第一层的没被改变,而第二层的就被改变了
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!