JavaScript拓展--浅拷贝与深拷贝

目录

浅拷贝

含义

基本类型浅拷贝

引用类型浅拷贝

常见实现方式 

深拷贝

含义

常见实现方式


        在练习代码时,我遇到了这样一个问题:当我把一个数组赋值给另外一个数组,用另外一个数组进行数据元素处理,输出结果时,发现原数组的数据元素随之发生相同改变。通过搜集资料,了解了这是由于浅拷贝造成的结果。故在此对于浅拷贝和深拷贝进行一个学习总结。由于原本练习代码较长,这里简约演示一下:

var arr1=[1,2,3,4,5];            //定义数组arr1=[1,2,3,4,5]
console.log("arr1=",arr1);       //输出数组arr1= [ 1, 2, 3, 4, 5 ]
var arr2=[];                     //定义空数组arr2
arr2=arr1;                       //将数组arr1赋值给arr2
console.log("arr2=",arr2);       //输出赋值后的数组arr2=[1,2,3,4,5]
for(var i=0;i<arr2.length;i++){  //将数组arr2逆置
    for(var j=i;j>0;j--){
        if(arr2[j-1]<arr2[j]){
            var temp=arr2[j-1];
            arr2[j-1]=arr2[j];
            arr2[j]=temp;
        }
    }
}
console.log("arr2=",arr2);       //输出逆置后的数组arr2= [ 5, 4, 3, 2, 1 ]
console.log("arr1=",arr1);       //输出数组arr1= [ 5, 4, 3, 2, 1 ]
arr1[0]=9;                       //改变数组arr1第一个元素为9
console.log("arr1=",arr1);       //输出数组arr1= [ 9, 4, 3, 2, 1 ]
console.log("arr2=",arr2);       //输出数组arr2= [ 9, 4, 3, 2, 1 ] 

        通过上面的代码可以发现,将数组arr1赋值给数组arr2后,对数组arr2内的元素进行逆置,可是arr1中的元素也随之改变,元素逆置,这就是浅拷贝,具体的下面进行分析

浅拷贝

含义

        浅拷贝是创建一个新对象,并拷贝原始对象属性。若原始对象属性是基本类型,拷贝的即是基本类型值,若原始对象属性是引用类型,拷贝的则是内存地址。故如果其中一个对象改变了这个对象的地址,就会影响到另一个对象(注意:浅拷贝只拷贝了数据对象的第一层,深层次的数据值和原始数据会互相影响。)

基本类型浅拷贝

        基本类型:简单的数据段,常见的有Number、String 、Boolean、Null和Undefined,基本数据类型是按值访问的,因为可以直接操作保存在变量中的实际值

//基本类型浅拷贝
var a=1;
var b=a;
b=2;
console.log("a="+a);  //输出a=1
console.log("b="+b);  //输出b=2

        由上例可以看见,将a拷贝给b,b值改变对a没有影响,因为b只是保存了a复制的一个副本,虽然两个变量值相等,但是分别保存了两个不同的基本数据类型值,所以b改变对a没有影响,我们可以用图来演示基本数据类型赋值过程: 

引用类型浅拷贝

        引用类型:由多个值构成的对象,也就是对象类型Object type,比如:Object 、Array 、Function 、Data等。引用数据类型时保存在堆内存中的对象,不能直接访问堆内存空间位置和操作堆内存空间,只能操作对象在栈内存中的引用地址。

//引用类型浅拷贝
var obj1=new Object();
var obj2=obj1;
obj2.name="zhangsan";
console.log("obj1=",obj1.name);  //输出obj1= zhangsan
console.log("obj2=",obj2.name);  //输出obj2= zhangsan

        由上例可以看见,将obj1赋值给obj2,改变obj2时,obj1也随之发生改变 。说明两个引用数据类型指向了同一个堆内存对象,我们可以理解为新对象和原对象共享内存,故其中一个对象改变其内存内容,其余对象都会随之发生改变。同样用图演示一下:

常见实现方式 

        上述已经阐明了引用类型和基本类型的区别,所以接下来都是以引用类型浅拷贝为例

1.Object.assign()

        语法:Object.assign(target,...sources)

        ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标target,剩下的参数是拷贝的源对象sources(可以是多个)

//Object.assign()
//浅拷贝只拷贝了数据对象的第一层,深层次的数据值和原始数据会互相影响
let obj1={name:"zhangsan",age:{num:20}};
let obj2=Object.assign({},obj1);
obj2.name="lirong";
obj2.age.num=18;
console.log(obj1);  //{ name: 'zhangsan', age: { num: 18 } }
console.log(obj2);  //{ name: 'lirong', age: { num: 18 } }

Object.assign()注意事项: 

        1.不拷贝对象不可枚举属性

        2.只拷贝源对象自身属性,不拷贝继承属性

        3.undefinednull无法转为对象,不能作为Object.assign参数,可作为源对象

2....扩展运算符

        语法:var cloneObj={...obj}

        该运算符是一个ES6/ES2015特性,与 Object.assign ()的功能相同

//...扩展运算符
let obj1={name:"zhangsan",age:{num:20}};
let obj2={...obj1};
obj2.name="lirong";
obj2.age.num=18;
console.log(obj1);  //{ name: 'zhangsan', age: { num: 18 } }
console.log(obj2);  //{ name: 'lirong', age: { num: 18 } } 

3. Array.prototype.concat()

        语法:var newArray=oldArray.concat(value1[,value2[,...[,valueN]]])

        参数:连接成新数组

//Array.prototype.concat()
let arr=[{a:1},{b:2}];
let arr1=[{c:3},{d:4}];
let arr2=arr.concat(arr1);
arr1[1].d=9;
console.log(arr2);  //[ { a: 1 }, { b: 2 }, { c: 3 }, { d: 9 } ]
console.log(arr1);  //[ { c: 3 }, { d: 9 } ]

4. Array.prototype.slice()

        语法:var newArray=Array.prototype.slice.call(oldArray);

//Array.prototype.slice()
let arr1=[{a:1},{b:2}];
let arr2=Array.prototype.slice.call(arr1);
arr2[1].b=5;
console.log(arr1);  //[ { a: 1 }, { b: 5 } ]
console.log(arr2);  //[ { a: 1 }, { b: 5 } ]

深拷贝

含义

        深拷贝时将一个对象从内存中完整拷贝一份出来,从堆内存中开辟一个新区域存放新对象,两者不会共享内存,故修改新对象不会影响原对象。

        如果用上述引用类型浅拷贝与深拷贝做对比的话,可以这样作图:

常见实现方式

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

        原理:利用JSON.stringify()把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串形式保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新对象(此方法可以处理数组或对象深拷贝,但是不能处理函数和正则)

//JSON.parse(JSON.stringify())
let obj1={name:'zhangsan',info:{age:22},fun:function(){}}
let obj2=JSON.parse(JSON.stringify(obj1));
obj2.name='lirong';
obj2.info.age='18';
console.log(obj1); //{ name: 'zhangsan', info: { age: 22 }, fun: [Function: fun] }
console.log(obj2); //{ name: 'lirong', info: { age: '18' } }
  • JSON.stringify()实现深拷贝注意点

        1.无法拷贝不可枚举属性

        2.无法拷贝对象原型链

        3.无法拷贝对象的循环应用(例如obj[key]=obj)

        4.拷贝Date引用类型会变成字符串

        5.拷贝RegExp引用类型会变成空对象

        6.若对象中含有NaN、Infinity和-Infinity,序列化结果变成null

        7.若拷贝对象值中含有函数、undefined、symbol,经过JSON.stringify()序列化后,JSON字符串中这个键值对消逝

2.循环遍历赋值

        该方法大多用于数组

//循环遍历拷贝
let arr1=[1,2,3,4,5];
let arr2=[];
for(let i=0;i<arr1.length;i++){
    arr2[i]=arr1[i]
}
arr2[0]=9;
console.log(arr2);  //[ 9, 2, 3, 4, 5 ]
console.log(arr1);  //[ 1, 2, 3, 4, 5 ]

3.递归方法 

        用递归方法实现深度克隆原理:遍历对象、数组指导里面都是基本数据类型

//递归方法
var obj={
    a:'普通属性',
    arr:[1,2,3],
    fn:function(){
        console.log("方法");
    }
}
function deepClone(obj){
    let newObj;
    if(typeof obj === 'object'){  //typeof运算符检测obj变量类型
        newObj=Array.isArray(obj)?[]:{}; //Array.isArray()用于确定传递的值是否是一个Array
        for(let key in obj){
            if(obj.hasOwnProperty(key)){ //hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
                newObj[key]=obj[key];
            }else{
                newObj[key]=deepClone(obj[key])
            }
        }
    }else{  //普通类型,直接赋值
        newObj=obj;
    }
    return newObj;
}
var deepClone=deepClone(obj);
console.log(deepClone); //{ a: '普通属性', arr: [ 1, 2, 3 ], fn: [Function: fn] }
obj.fn='change';
console.log(obj);       //{ a: '普通属性', arr: [ 1, 2, 3 ], fn: 'change' }
console.log(deepClone); //{ a: '普通属性', arr: [ 1, 2, 3 ], fn: [Function: fn] }

         除了上述方法,还有其他方法可以实现深拷贝,例如利用函数库lodash的_.cloneDeep方法还有jQuery.extend()方法等,由于目前学习进度还没有到这里,所以暂时总结两种深拷贝方法,之后进行补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值