0
点赞
收藏
分享

微信扫一扫

深入学习javaScript中的深拷贝(复制)和浅拷贝(复制)


学习深拷贝和浅拷贝之前,首先我们需要知道什么是基本数据类型和引用数据类型

嗯、步入正题


所谓的浅复制,只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用(仅仅是指向被复制的内存地址,如果原地址改变了,那么浅复制出来的对象也会相应的改变),我们把这种拷贝叫做“(浅复制)浅拷贝”。


而深复制的话,深复制在计算机中开辟了一块内存地址用于存放复制的对象,所以复制出来的对象和原对象之间不会相互影响。

深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。

下面我们先来看看浅拷贝的例子

首先看的是基本类型的复制

var a=123;
var b=a;
a=123456;
console.log(a); //123456
console.log(b); //123

//或者是

var a='afafas';
var b=a;
a='fgfdsdsgs';
console.log(a); //fgfdsdsgs
console.log(b); //afafas

像这种基本数据类型,我们进行简单的复制,不会发生引用

然后再来看看引用类型(对象)的浅拷贝

我们知道对象的话,我们直接赋值(浅拷贝)是会发生相互引用的,原对象和新对象之间不管谁改变都会影响到另一个,因为这种复制,只是将复制出来的东西的指向指向了要复制的那个东西的内存地址,简单的说,就是两个都同时指向了一个空间,如果改变其中一个,另一个也会发生变化。这就发生了引用。

看例子呗

var person={name:'leo',age:18};

var son={sex:'男'};

function clone(p,s){

  var s=s||{};//判断s对象是否存在,不存在则定义s为空对象

  for(var prop in p){

    s[prop]=p[prop];
};
    return s;//返回s对象

};

clone(person,son);

调用clone函数最终返回的结果为 {sex: "男", name: "leo", age: 18}.

但是假如 var person={name:'leo',age:18,man:{hight:188}};即person 对象里包含一个子对象man;

那么在复制时浅拷贝只是将子对象的一个引用地址复制给son对象;son和person里的man 属性都指向同一个对象,故改变任一个都能影响到另外一个:例子如下:


var person={name:'chen',age:18,man:{hight:188}}

var son={sex:'男'};


function clone(p,s){

  var s=s||{};//判断s对象是否存在,不存在则定义s为空对象

  for(var prop in p){

    s[prop]=p[prop];

};

    return s;//返回s对象

};

clone(person,son);

son.name='ze';

console.log(son.name);//ze

console.log(person.name);//leo

son.man.hight=200;

console.log(son.man.hight);//200

console.log(person.man.hight);//200


2、

Object.assign()是ES6新增函数,Object.assign()方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是Object.assign()

Object.assign(target, ...sources)

参数:

target:目标对象。
sources:任意多个源对象。
返回值:目标对象会被返回。

var obj = { a: {a: "hello", b: 21} };
var initalObj = Object.assign({}, obj);

initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"

3、jQuery中的$.extend()默认也是浅拷贝

var person={name:'leo',age:18}

var son={sex:'男'};

$.extend(son,person);
console.log(son);//{sex: "男", name: "leo", age: 18}


想要解决两个对象之间的相互引用问题,就需要用到深拷贝喽,因为我们前边已经说过了深复制在计算机中开辟了一块内存地址用于存放复制的对象,所以复制出来的对象和原对象之间不会相互影响。

那么深拷贝(深复制)到底怎么实现的呢?办法有很多了。针对上面的这个问题


1、采用递归的方法去拷贝要复制的对象,看例子咯

var person={name:'leo',age:18,man:{hight:188}}

var son={sex:'男'};

function clone(p,s){
  var s=s||{};//判断s对象是否存在,不存在则定义s为空对象

  for(var prop in p){

    if(typeof p[prop]=='object'){

        s[prop]=(p[prop].constructor===Array)?[]:{};//三元运算,将s[prop]初始化为数组或者对象

        clone(p[prop],s[prop])

    }

    else{

      s[prop]=p[prop];
    }
    
};

    return s;//返回s对象

};


clone(person,son);

son.name='ze';

console.log(son.name);//ze

console.log(person.name);//leo
son.man.hight=200;

console.log(son.man.hight);//200
console.log(person.man.hight);//188

2、利用jQuery中的$.extend()方法喽

$.extend(),默认是浅拷贝,但如果我们将第一个参数设为true,即为深拷贝,即$.extend(true,b,a)的形式

嗯、看例子吧

var person={name:'leo',age:18,man:{hight:188}}

var son={sex:'男'};
$.extend(true,son,person);
son.name='ze';

console.log(son.name);//ze

console.log(person.name);//leo

son.man.hight=200;

console.log(son.man.hight);//200

console.log(person.man.hight);//188

当然,还有别的方法,但目前鄙人还没深入研究,所以就不咋这里误人子弟,以后有时间也会做相应的研究再做更新

2、对象只有一层的话可以使用:函数

Object.assign({},obj1)的意思是先建立一个空对象{},接着把obj1中所有的属性复制过去,所以​​obj2​​会长得跟​​obj1​​一样,这时候再修改​​obj2.b​​​也不会影响​​obj1。​


因为​​Object.assign​​跟我们手动复制的效果相同,所以一样只能处理深度只有一层的对象,没办法做到真正的 深拷贝。不过如果要复制的对象只有一层的话可以考虑使用它。如下

var 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 }

JSON对象中的parse和stringify,JOSN对象中的stringify可以把一个js对象序列化为一个JSON字符串,parse可以把JSON字符串反序列化为一个js对象,通过这两个方法,也可以实现对象的深复制。

var 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 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false

这样做是真正的深拷贝这种方法简单易用。

但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。

这种方法能正确处理的对象只有Number,String,Boolean,Array,扁平对象、即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。

也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。

var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 没复制

要复制的function会直接消失,所以这个方法只能用在单纯只有数据的对象。


那么对于数组,ES6我们复制有新的两种方法,不会发生引用。

第一种:Array.from(要复制的数组);

var arr1=[1,2,3];
var arr2=Array.from(arr1);
arr1.push(4);
alert(arr1); //1234
alert(arr2); //123
arr2.push(5);
alert(arr1); //1234
alert(arr2); //1235


第二种:...

var arr1=[1,2,3];
var arr2=[...arr1];
arr1.push(4);
alert(arr1); //1234
alert(arr2); //123
arr2.push(5);
alert(arr1); //1234
alert(arr2); //1235

第二种这个方法也可以用在函数的行参上面。

function show(...arr1){  //直接来复制arguments这个伪数组,让它变成真正的数组,从而拥有数组的方法。
alert(arr1); //1234
arr1.push(5);
alert(arr1); //12345
}
show(1,2,3,4)


或者是通过循环来复制:

var arr1=[1,2,3,4];
var arr2=[];
for(var i=0; i<arr1.length; i++){
arr2[i]=arr1[i];
}
arr1.push(5);
arr2.push(6);
alert(arr1); //12345
alert(arr2); //12346





举报

相关推荐

0 条评论