JavaScript之浅复制【拷贝】与深复制【拷贝】【二】

828 阅读6分钟

下面了解下什么浅复制【拷贝】和深复制【拷贝】,通过下面的阅读你将了解到:

1、什么是浅复制以及使用场景
2、什么是深复制以及使用场景
3、浅复制和深复制有哪些方式

一、我们先来了解下,JavaScript基本知识,基本类型和引用类型

基本类型:number、string、boolean、null、undefined 后来es6又增加了一个基本类型symbol,目前基本类型为6个
引用类型:Object、 Function、Array

什么是堆【heap】和 栈 【stack

栈:自动分配内存,系统自动释放,里面包含值类型和引用类型的地址【引用对象】
堆:动态分配内存,大小不定,系统不会自动释放,里面存放引用类型的值【实例对象】

看个例子:

let a=1;
let b=a;
console.log(a)//1
console.log(b)//1
b=3;
console.log(b)//3
console.log(a)//1

从例子得出结论:当值赋值给变量时,解析器确定是基本类型值还是引用类型值。基本数据类型,是值访问的,并且可以操作保存在变量中的实际值。基本类型复制的时候,就是在栈中开辟一个新的存储区域用来存储变量。所以其中一个值变化,不会影响另一个值.
如例子:虽然b=3发生了改变,但a输出结果还是1,印证上面的结论.

再来看下个例子:

let obj={name:'zjl',remark:'zjl'};
let anotherObj=obj;
anotherObj.name='lisi'
console.log(obj);//{name:'lisi',remark:'zjl'};

从上面的例子中,可以看出一个值发生的改变也影响到了另一个.
如上:obj的name由zjl变为了lisi,这是为什么呢?

引用类型值【实例对象】,是存放在堆内存中的对象。与其它语言不同,JavaScript中,不允许直接访问内存位置及不能直接操作内存空间。实际操作的是对象的引用【指针】,不是实际的对象。

补充:对象引用是栈中的地址,复制对象时,相当于在栈中开辟新的一块区域存放这个地址(指针),这个指针指向同一块堆内存位置。所以其中一个指针对象发生改变,另一个也会发生改变。

总结下区别,两者的主要区别就是,基本类型是值传递,引用类型是地址传递

二、下面就,常见的数组【Array】和对象【Object】,来讨论下深浅复制

  • 浅复制:拷贝的是引用对象--拷贝的是栈中的地址【对象的引用】,栈中指针指向同一块堆内存【实例对象】,因此修改一个引用对象,另一个引用对象也随之修改.就像你拿了一把钥匙打开一扇门,别人拿到你这把钥匙也能打开这扇门,并且从房间拿走了东西,你拿到这把钥匙开门后发现东西少了,简言之也就是共享了同一个房间,JavaScript中就是同一块堆内存.
  • 深复制:拷贝的是实例对象。因实例对象放在堆内存中的,要想实现深拷贝,必须重开辟一块堆内存,新创建原对象的实例,并保证不同的对象引用。使原对象与新建对象完全隔离互不影响。

1、浅复制【拷贝】

//数组浅复制
let arr=[1,2,3,5,8];
let item=[];
for(let i in arr){
    item[i]=arr[i];
}
item.push(9);
console.log('arr==>'+arr);//arr==>1,2,3,5,8
console.log('item==>'+item);//item==>1,2,3,5,8,9
//对象浅复制
let obj={name:'zjl',age:'28'};
let list={};
for(let i in obj){
    list[i]=obj[i];
}
list['like']='apple';
console.dir('obj===>'+JSON.stringify(obj));
console.dir('list===>'+JSON.stringify(list));

obj===>{"name":"zjl","age":"28"}
list===>{"name":"zjl","age":"28","like":"apple"}

以上例子可以看出,数组、对象实现了浅复制.但是上面的代码只能实现一层的拷贝,无法实现深层的拷贝,如果把上面的代码改动下,如下:

//对象浅复制
let obj={name:'zjl',age:'28'};
let list={};
for(let i in obj){
    list[i]=obj[i];
}
list['name']='lisi';
console.dir('obj===>'+JSON.stringify(obj));
console.dir('list===>'+JSON.stringify(list));

obj===>{"name":"lisi","age":"28"}
list===>{"name":"lisi","age":"28"}

上面的例子,可以看出对象name被改变了.影响了原始的对象值.因引用类型为地址传递,没有开辟新的堆内存,地址指向同一块内存位置,所以改变一个对象另一个对象也会随之改变。所以无法实现深层次的复制【拷贝】.怎样解决这个问题,我们需要使用深拷贝【复制】来完成,继续往下看...

2、深复制【拷贝】: 看下面的例子,递归实现深层复制:

var china = {
    nation: '中国',
    adrress: ['北京', '上海', '广州'],
}
//深复制,要想达到深复制就需要用递归
function deepCopy(o, c) {
    var c = c || {}
    for (var i in o) {
        if (typeof o[i] === 'object') { //要考虑深复制问题了
            if (o[i].constructor === Array) {
                //这是数组
                c[i] = []
            } else {
                //这是对象
                c[i] = {}
            }
            deepCopy(o[i], c[i])
        } else {
            c[i] = o[i]
        }
    }
    return c
}
var result = {}
result = deepCopy(china, result);
result.nation = '美国';
console.dir(JSON.stringify(result)); //{"adrress":["北京","上海","广州"],"nation":"美国"}
console.dir(JSON.stringify(china)); //{"adrress":["北京","上海","广州"],"nation":"中国"}

从上面的例子,可以看出已经实现了,真正的复制.下面看下图解:

三、深复制【拷贝】方法还有很多种

深复制【拷贝】后,两个对象,包括其内部的元素互不干扰。

1、JSON.parse(JSON.stringify())反序列化
2、JQuery自带的,$.extend(true,{},obj);
3、loadsh.js的实现_.cloneDeep和_.clone(value, true)

感兴趣的可以去了解下

鉴于有朋友评论对于JSON.parse(JSON.stringify())反序列化存在局限性,现在做以补充: 照旧,还是先看下例子: 1、对数组对象进行深层次的拷贝

let obj={
like:'color',
list:{
    item:['green','red']
}}

let deepCopy = (JSON.parse(JSON.stringify(obj)));
deepCopy.list.item[0]='yellow';
console.log(deepCopy);//{like:'color',list:{item:['yellow','red']}
console.log(obj);//{like:'color',list:{item:['green','red']}

2、针对undefined,function,symbol的拷贝

let obj={x: undefined, y: Object, z: Symbol("")};
console.log('序列化==>'+JSON.stringify(obj)); // 序列化==> '{}'
console.log('反序列化==>'+JSON.parse(JSON.stringify(obj))); // 反序列化==> {} 
// 对应一个原型 __proto__

let arr=[undefined, Object, Symbol("")];
console.log('序列化==>'+JSON.stringify(arr)); 
// 序列化==> '[null,null,null]'
console.log('反序列化 arr==>'+JSON.parse(JSON.stringify(arr)));  
// 反序列化 arr==> [null,null,null]
// 反序列化 对应一个原型 __proto__

从以上实例得出以下结论:

undefined、任意的函数以及symbol值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)

JSON.parse(JSON.stringify())虽不能对undefined,function,symbol进行深拷贝,但使用起来简单,可以满足大部分的场景,具体还是要根据需要选择使用.

JavaScript之闭包【三】

欢迎关注,【前端突击】 猎鹰突击,迎难而上,期待你的加入...