早上好呀!今天向大家分享一下JS里的拷贝!之前也是看过许多关于拷贝的文章,最后还是总结,想要分享给大家啦!
JS拷贝分为深浅拷贝!
一 js数据类型
js数据有不同的划分方式,此处以数据在内存中的储存形式划分,可以分为两种类型:基本类型和复杂类型
- 基本类型主要包括:string,number,boolean,undefined,null,symbol
- 复杂类型主要包括:object,array,function
二 js中的栈堆
在js引擎中对数据的存储主要有两种位置,堆内存和栈内存。
栈内存主要用于存储各种基本类型的变量,包括string,number,boolean,undefined,null,symbol,以及复杂类型的指针。
而堆内存主要负责复杂数据(object,array,function)的内容部分的存储。
栈
- 由系统自动分配释放;
- 一般有固定大小和范围上限;
3.存放基本数据或复杂数据的指针等;
4.读取顺序是先进后出。
堆
- 一般由程序员分配释放, 若程序员不释放,程序结束时可能由系统回收;
- 存储大小未知,由程序执行时产生的数据大小自动确定;
- 用于存放复杂数据;
- 读取顺序是先进先出。
三 数据的拷贝
数据拷贝,一般指栈堆中的栈拷贝。如果是基本数据,那么拷贝的就是数据;如果是复杂数据,那么拷贝的是指针,可能会导致多个指针指向同一个对象。
具体体现为,基本数据拷贝后,修改拷贝后的数据,不会被影响原数据;复杂数据拷贝后,修改拷贝后的数据,会作用到原数据,因为此时修改的是同一个指针指向的数据。
1. 基本数据的拷贝
var a = 10;
var b = a;
console.log(a); // 10
console.log(b); // 10
var c = 10;
var d = c; // 拷贝的是数据
d = 20; // 修改拷贝之后的数据
console.log(c); // 10
console.log(d); // 20
2. 复杂数据的拷贝
var obj = { a: 10 };
var obj2 = obj;
console.log(obj); // { a: 10 }
console.log(obj2); // { a: 10 }
var obj = { a: 10 };
var obj2 = obj; // 拷贝的只是指针,不是数据
obj2.a = 20; // 修改拷贝之后的数据
console.log(obj); // { a: 20 }
console.log(obj2); // { a: 20 }
四 对象的拷贝
所谓对象的拷贝,其实就是基于复杂数据在拷贝时的异常处理,我们将复杂数据的默认拷贝定义为浅拷贝;就是只拷贝复杂数据的地址,而不拷贝值。那么与之对应的就是不拷贝地址,只拷贝值,也被称为深拷贝。
一般情况下,等号赋值,函数传参,都是浅拷贝,也就是只拷贝了数据的地址。
let foo = {
title:"hello obj"
}
// 等号赋值
let now = foo;
now.title = "this is new title";
console.log(foo.title); // this is new title
console.log(now.title); // this is new title
// 函数传参
function change(o){
o.title = "this is function change title";
}
change(foo);
console.log(foo.title); // this is function change title
如果要实现拷贝数据的值,需要借助一些措施,或处理方式。如:
1. 遍历复杂数据,拷贝内部属性或值
let foo = {
title:"hello obj"
}
let now = {};
for(let attr in foo){
now[attr] = foo[attr];
}
// 深拷贝成功
console.log(foo === now); // false
now.title = "this is new title";
console.log(foo.title); // hello obj
console.log(now.title); // this is new title
这种方式比较复杂,并且在遇到多层复杂数据嵌套时,只能深拷贝一层数据,如果修改内层复杂数据,依然会影响原数据,无法解决本质问题
let foo = {
title:{
sTitle:"this is small title"
}
}
let now = {};
for(let attr in foo){
now[attr] = foo[attr];
}
// 外层对象深拷贝成功
console.log(foo === now); // false
// 内层对象依然是浅拷贝
console.log(foo.title === now.title); // true
now.title.sTitle = "this is new small title";
console.log(foo.title.sTitle); // this is new small title
console.log(now.title.sTitle); // this is new small title
但可以利用函数递归的方式解决这个问题 ---- 强烈推荐
function deepClone(target){
// 判断要处理的目标是数组还是对象
let newObj = target.constructor === Array ? [] : {};
// 遍历目标:in运算符会拿到原型上继承到的属性
for (let attr in target){
// 判断属性是否在当前对象自身
if (target.hasOwnProperty(attr)) {
// 如果值是对象或数组,就递归一下
if (target[attr] && typeof target[attr] === "object") {
newObj[attr] = target[attr].constructor === Array ? [] : {};
newObj[attr] = deepClone(target[attr]);
}else if(typeof target[attr] === "function"){
// 如果是函数,就复制一个新函数
newObj[attr] = target[attr].bind(target);
}else{
// 如果不是对象,数组,函数,就直接赋值
newObj[attr] = target[attr];
}
}
}
return newObj;
}
// 测试
let foo = {
title:{
sTitle:"this is small title",
show:function(){
console.log(this);
},
list:["hello","world",{a:10,b:20}]
}
}
let now = deepClone(foo);
// 外层对象深拷贝成功
console.log(foo === now); // false
// 内层对象深拷贝成功
console.log(foo.title === now.title); // false
// 内层函数深拷贝成功
console.log(foo.title.show === now.title.show); // false
// 内层数组深拷贝成功
console.log(foo.title.list === now.title.list); // false
// 内层属性深拷贝成功
now.title.sTitle = "this is new small title";
console.log(foo.title.sTitle); // this is small title
console.log(now.title.sTitle); // this is new small title
2. 利用js中对json的解析方法
let foo = {
title:{
sTitle:0,
list:[1,2,{a:3,b:4}]
}
}
let now = JSON.parse(JSON.stringify(foo));
console.log(foo); // {title: {sTitle: 0, list: [1, 2, {a: 3, b: 4}]}}
console.log(now); // {title: {sTitle: 0, list: [1, 2, {a: 3, b: 4}]}}
// 深拷贝成功
console.log(foo === now); // false
可以拷贝多层对象,但受json数据的限制,无法拷贝函数,undefined,NaN属性
let foo = {
title:{
show:function(){},
num:NaN,
empty:undefined
}
}
let now = JSON.parse(JSON.stringify(foo));
// 虽然实现了深拷贝,但show和empty属性丢失,num属性被转成了null
console.log(foo); // {title: {{num: NaN, empty: undefined, show: ƒ}}}
console.log(now); // {title: {num: null}}
console.log(foo === now); // false
3. 利用ES6 提供的 Object.assign()
let foo = {
title:{
show:function(){},
num:NaN,
empty:undefined
}
}
let now = {};
Object.assign(now, foo);
console.log(foo); // {title: {{num: NaN, empty: undefined, show: ƒ}}}
console.log(now); // {title: {{num: NaN, empty: undefined, show: ƒ}}}
// 外层对象深拷贝成功
console.log(foo === now); // false
// 内层对象依然是浅拷贝
console.log(foo.title === now.title); // true
只能可以拷贝一层数据,无法拷贝多层数据
4. 利用ES6 提供的展开运算符:...
let foo = {
title:{
show:function(){},
num:NaN,
empty:undefined
}
}
let now = {...foo};
console.log(foo); // {title: {{num: NaN, empty: undefined, show: ƒ}}}
console.log(now); // {title: {{num: NaN, empty: undefined, show: ƒ}}}
// 外层对象深拷贝成功
console.log(foo === now); // false
// 内层对象依然是浅拷贝
console.log(foo.title === now.title); // true
5. 数组拷贝concat 方法
语法:arr1.concat(arr2)
concat() 方法用于连接两个或多个数组。
返回值:返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组。
该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
当数组的元素为一维时是深拷贝
一维以上是赋值引用
6. 数组拷贝slice方法
语法:arr.slice(start,end)
slice() 方法可从已有的数组中返回选定的元素。
返回值:返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。
- 没有参数是拷贝数组
- 只有一个参数是从该位置起到结束拷贝数组元素
- 两个参数,拷贝从起始位置到结束位置的元素(含头不含尾)
当数组中的元素是一维时是深拷贝
数组中元素是一维以上时是赋值引用
7.使用函数库lodash中的cloneDeep()方法
- 下载模块
cnpm i lodash --save 或 yarn add lodash
- 导入模块
import _ from 'lodash'
- 使用cloneDeep()方法对数据进行深拷贝
loodash.cloneDeep(obj)
//深拷贝
8. 使用immutable-js
【简单学习】https://www.jianshu.com/p/2ae0507ed86d
【常见API简介】https://segmentfault.com/a/1190000010676878
总结
1. 数组有slice方法,concat方法等,也可以实现深拷贝,但只能对数组进行操作,不能操作对象;
2. 直接使用赋值运算符复制对象,只拷贝了地址,没有拷贝值,是浅拷贝;
3. 直接遍历对象,使用赋值运算符拷贝属性,只能拷贝单层数据,无法拷贝多层数据;
4. json数据和对象的解析,可以拷贝多层数据,但是如果对象中存在function,undefined,NaN,就会发生数据丢失;
5. js自身提供的诸多方法,如:Object.assign,展开运算符...,数组的slice,数组的concat等,只能拷贝单层数据,无法拷贝多层数据;
6. 使用递归思想,封装函数,借助赋值运算符,才是真正的深拷贝。最终解决方案:递归!
7.使用函数库中lodash的cloneDeep()方法
8.使用immutable-js
今天的分享就到这里了!谢谢大家!我要去睡觉了~~~