对象的深拷贝与浅拷贝

浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用(堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,object array 则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。

浅拷贝:浅拷贝的意思就是只复制引用(指针),而未复制真正的值。

const originArray = [1,2,3,4,5];

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneArray = originArray;

const cloneObj = originObj;

console.log(cloneArray); // [1,2,3,4,5]

console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}

cloneArray.push(6);

cloneObj.a = {aa:'aa'};

console.log(cloneArray); // [1,2,3,4,5,6]

console.log(originArray); // [1,2,3,4,5,6]

console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}

console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}

上面的代码是最简单的利用=赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着cloneArray和cloneObj改变,originArray和originObj也随着发生了变化。

深拷贝:深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。

只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。

目前实现深拷贝的方法不多,主要是两种:

利用 JSON 对象中的 parse 和 stringify

利用递归来实现每一层都重新创建对象并赋值

JSON.stringify/parse的方法

先看看这两个方法吧:

The JSON.stringify() method converts a JavaScript value to a JSON string.

JSON.stringify是将一个JavaScript值转成一个JSON字符串。

The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.

JSON.parse是将一个JSON字符串转成一个JavaScript值或对象。

很好理解吧,就是JavaScript值和JSON字符串的相互转换。

它能实现深拷贝呢?我们来试试。

const originArray = [1,2,3,4,5];

const cloneArray = JSON.parse(JSON.stringify(originArray));

console.log(cloneArray === originArray); // false

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneObj = JSON.parse(JSON.stringify(originObj));

console.log(cloneObj === originObj); // false

cloneObj.a = 'aa';

cloneObj.c = [1,1,1];

cloneObj.d.dd = 'doubled';

console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};

console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

 确实是深拷贝,也很方便。但是,这个方法只能适用于一些简单的情况。比如下面这样的一个对象就不适用:

const originObj = {

  name:'axuebin',

  sayHello:function(){

    console.log('Hello World');

  }

}

console.log(originObj); // {name: "axuebin", sayHello: ƒ}

const cloneObj = JSON.parse(JSON.stringify(originObj));

console.log(cloneObj); // {name: "axuebin"}

发现在cloneObj中,有属性丢失了。。。那是为什么呢?

undefined、function、symbol会在转换过程中被忽略。。。

If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).

  明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝

递归的方法

递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值的操作,简单粗暴上代码:

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;

}

  我们来试试:

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneObj = deepClone(originObj);

console.log(cloneObj === originObj); // false

cloneObj.a = 'aa';

cloneObj.c = [1,1,1];

cloneObj.d.dd = 'doubled';

console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};

console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

可以。那再试试带有函数的:

const originObj = {

  name:'axuebin',

  sayHello:function(){

    console.log('Hello World');

  }

}

console.log(originObj); // {name: "axuebin", sayHello: ƒ}

const cloneObj = deepClone(originObj);

console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}

  也可以。搞定。

JavaScript中的拷贝方法

我们知道在 JavaScript 中,数组有两个方法 concat 和 slice 是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。

同时,ES6 中 引入了 Object.assgn 方法和 ... 展开运算符也能实现对对象的拷贝。

那它们是浅拷贝还是深拷贝呢?

concat

The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.

该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。

看着这意思,很像是深拷贝啊,我们来试试:

const originArray = [1,2,3,4,5];

const cloneArray = originArray.concat();

console.log(cloneArray === originArray); // false

cloneArray.push(6); // [1,2,3,4,5,6]

console.log(originArray); [1,2,3,4,5];

看上去是深拷贝的。

我们来考虑一个问题,如果这个对象是多层的,会怎样。

const originArray = [1,[1,2,3],{a:1}];

const cloneArray = originArray.concat();

console.log(cloneArray === originArray); // false

cloneArray[1].push(4);

cloneArray[2].a = 2;

console.log(originArray); // [1,[1,2,3,4],{a:2}]

originArray 中含有数组 [1,2,3] 和对象 {a:1},如果我们直接修改数组和对象,不会影响 originArray,但是我们修改数组 [1,2,3] 或对象 {a:1} 时,发现 originArray 也发生了变化。

结论:concat 只是对数组的第一层进行深拷贝。

slice

The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.

解释中都直接写道是a shallow copy了 ~

但是,并不是!

const originArray = [1,2,3,4,5]; const cloneArray = originArray.slice(); console.log(cloneArray === originArray); // false cloneArray.push(6); // [1,2,3,4,5,6] console.log(originArray); [1,2,3,4,5];

  同样地,我们试试多层的数组。

const originArray = [1,[1,2,3],{a:1}];

const cloneArray = originArray.slice();

console.log(cloneArray === originArray); // false

cloneArray[1].push(4);

cloneArray[2].a = 2;

console.log(originArray); // [1,[1,2,3,4],{a:2}]

果然,结果和concat是一样的。

结论:slice只是对数组的第一层进行深拷贝。

Object.assign()

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target obj

结论:Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

... 展开运算符

const originArray = [1,2,3,4,5,[6,7,8]];

const originObj = {a:1,b:{bb:1}};

const cloneArray = [...originArray];

cloneArray[0] = 0;

cloneArray[5].push(9);

console.log(originArray); // [1,2,3,4,5,[6,7,8,9]]

const cloneObj = {...originObj};

cloneObj.a = 2;

cloneObj.b.bb = 2;

console.log(originObj); // {a:1,b:{bb:2}}

结论:...实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。

首层浅拷贝

我们知道了,会有一种情况,就是对目标对象的第一层进行深拷贝,然后后面的是浅拷贝,可以称作“首层浅拷贝”。

我们可以自己实现一个这样的函数:

function shallowClone(source) {

  const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象

  for (let keys in source) { // 遍历目标

    if (source.hasOwnProperty(keys)) {

      targetObj[keys] = source[keys];

    }

  }

  return targetObj;

}

  我们来测试一下:

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneObj = shallowClone(originObj);

console.log(cloneObj === originObj); // false

cloneObj.a='aa';

cloneObj.c=[1,1,1];

cloneObj.d.dd='surprise';

经过上面的修改,cloneObj 不用说,肯定是 {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}} 了,那 originObj 呢?刚刚我们验证了 cloneObj === originObj 是 false,说明这两个对象引用地址不同啊,那应该就是修改了 cloneObj 并不影响 originObj。

console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}}

console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'surprise'}}

What happend?

originObj 中关于 a、c都没被影响,但是 d 中的一个对象被修改了。。。说好的深拷贝呢?不是引用地址都不一样了吗?

原来是这样:

从 shallowClone 的代码中我们可以看出,我们只对第一层的目标进行了 深拷贝 ,而第二层开始的目标我们是直接利用 = 赋值操作符进行拷贝的。

so,第二层后的目标都只是复制了一个引用,也就是浅拷贝。

总结

赋值运算符=实现的是浅拷贝,只拷贝对象的引用值;

JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;

JSON.stringify实现的是深拷贝,但是对目标对象有要求(非 undefined,function);

若想真正意义上的深拷贝,请递归。

本文转载自:https://www.cnblogs.com/dabingqi/p/8502932.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容