数据类型
Javascript中的数据类型分为两种,基本数据类型和引用数据类型。基本数据类型包括:Number,String,Undefined,Null,Boolean,Symbol,BigInt。引用数据类型包括:对象(Object),数组(Array),函数(Function)。
基本类型是按值访问
的,不会影响到其他数据,如:
let a = '前端'
let b = a
a = '前端工程师'
b //前端
所有基本数据类型的值没有深拷贝的概念,js中的浅拷贝和深拷贝,是针对引用数据类型(如Object,Array,Function)的复制问题。浅拷贝和深拷贝都可以实现在已有对象上再生出一份的作用。
深拷贝 VS 浅拷贝
深拷贝和浅拷贝的示意图大致如下:
浅拷贝: 只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存
深拷贝: 会创造一个一摸一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
浅拷贝实现方式
1、Object.assign()
let obj = { a: {a: "hello", b: 21} };
let initalObj = Object.assign({}, obj);
initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"
注意: 当object只有一层的时候,是深拷贝
。例子如下:
let obj1 = {a:10,b:20,c:30}
let 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}
2、Array.prototype.slice()
let arr = [1,3,{username: 'jack'}]
let arr2 = arr.slice()
arr2[2].username = 'ross'
console.log(arr) // [1,3,{username: 'ross'}]
修改新对象会影响到原对象
3、Array.prototype.concat()
let arr = [1,3,{username: 'jack'}]
let arr2 = arr.concat()
arr2[2].username = 'ross'
console.log(arr) //[1,3,{username: 'ross'}]
同样,修改新对象也会改到原对象。
关于数组的slice和concat方法的补充说明: 数组的slice和concat方法不会修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
原数组的元素会按照以下规则拷贝:
- 如果该元素是个对象引用(不是实际的对象),slice或concat会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
- 对于字符串、数字及布尔值来说(不是String、Number或者Boolean对象),slice会拷贝这些值到新的数组里,在别的数组里修改这些字符串或是数字或是布尔值,将不会影响另一个数组。
总结来说:
- 当数组里的元素数据类型为字符串、数字及布尔,使用slice或concat方法再复制一份出来,修改新数组不会影响原数组,是深拷贝。
- 当数组里的元素数据类型为引用类型,使用slice或concat方法再复制一份出来,修改新数组会影响原数组,是浅拷贝
示例如下:
let arr = [1, 3, {
username: 'jack'
}];
let arr2 = arr.slice();
arr2[1] = 2
console.log(arr); // [1,3,{username:'jack'}]
console.log(arr2); // [1,2,{username:'jack'}]
深拷贝实现方式
1、JSON.parse(JSON.stringify())
let arr = [1, 3, {
username: 'ross'
}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'jack';
console.log(arr) //[1, 3, { username: 'ross'}];
console.log(arr2) //[1, 3, { username: 'jack'}];
原理: 用JSON.stringify()将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一来一回之间,新的对象产生了,而且新对象会开辟新的栈,实现深拷贝。
缺点: 此方法虽然可以实现数组或对象深拷贝,但是不能处理函数
2、手写递归方法
递归实现思路: 遍历对象、数组,对每一层的数据都实现一次创建对象、对象赋值的操作,就是深拷贝
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
var obj = {
name: 'Hanna',
age: 22
}
var objCopy = deepClone(obj)
obj.name = 'ding';
console.log(obj);//Object {name: "ding", age: 22}
console.log(objCopy);//Object {name: "Hanna", age: 22}
参考文章
浅拷贝与深拷贝
深入JavaScript基础之深浅拷贝
什么是js深拷贝和浅拷贝及其实现方式
JavaScript浅拷贝和深拷贝