深拷贝和浅拷贝

在 JS 中有一些基本类型像是Number、String、Boolean,而对象就是像这样的东西{ name: 'Larry', skill: 'Node.js' },对象跟基本类型最大的不同就在于他们的传值方式。

基本类型是按值传递,像是这样:在修改a时并不会改到b

vara =25;

varb =a;

b=18;

console.log(a);//25

console.log(b);//18

但对象就不同,对象传的是按引用传值:

varobj1 = { a:10, b:20, c:30};

varobj2 =obj1;

obj2.b=100;

console.log(obj1);

//{ a: 10, b: 100, c: 30 } <-- b 被改到了

console.log(obj2);

//{ a: 10, b: 100, c: 30 }

复制一份obj1叫做obj2,然后把obj2.b改成100,但却不小心改到obj1.b,因为他们根本是同一个对象,这就是所谓的浅拷贝。

要避免这样的错误发生就要写成这样:

varobj1 = { a:10, b:20, c:30};

varobj2 ={ a: obj1.a, b: obj1.b, c: obj1.c };

obj2.b=100;

console.log(obj1);

//{ a: 10, b: 20, c: 30 } <-- b 沒被改到

console.log(obj2);

//{ a: 10, b: 100, c: 30 }

这样就是深拷贝,不会改到原本的obj1。

浅拷贝(Shallow Copy) VS 深拷贝(Deep Copy)

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

浅拷贝的实现方式

也就是简单地复制而已

1、简单地复制语句

function simpleClone(initalObj) {

varobj ={};

for(variininitalObj) {

obj[i]=initalObj[i];

}

returnobj;

}

varobj ={

a:"hello",

b:{

a:"world",

b:21

},

c:["Bob","Tom","Jenny"],

d:function() {

alert("hello world");

}

}

varcloneObj =simpleClone(obj);

console.log(cloneObj.b);

console.log(cloneObj.c);

console.log(cloneObj.d);

cloneObj.b.a="changed";

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

cloneObj.d= function() { alert("changed"); };

console.log(obj.b);

console.log(obj.c);

console.log(obj.d);

结果为:

2、Object.assign()

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

Object.assign(target, ...sources)

参数:

target:目标对象。

sources:任意多个源对象。

返回值:目标对象会被返回。

varobj = { a: {a:"hello", b:21} };

varinitalObj =Object.assign({}, obj);

initalObj.a.a="changed";

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

兼容性:

需要注意的是:

Object.assign()可以处理一层的深度拷贝,如下:

varobj1 = { a:10, b:20, c:30};

varobj2 =Object.assign({}, obj1);

obj2.b=100;

console.log(obj1);

//{ a: 10, b: 20, c: 30 } <-- 沒被改到

console.log(obj2);

//{ a: 10, b: 100, c: 30 }

深拷贝的实现方式

要完全复制又不能修改到原对象,这时候就要用 Deep Copy,这里会介绍几种Deep Copy 的方式。

1、手动复制

把一个对象的属性复制给另一个对象的属性

varobj1 = { a:10, b:20, c:30};

varobj2 ={ a: obj1.a, b: obj1.b, c: obj1.c };

obj2.b=100;

console.log(obj1);

//{ a: 10, b: 20, c: 30 } <-- 沒被改到

console.log(obj2);

//{ a: 10, b: 100, c: 30 }

但这样很麻烦,要一个一个自己复制;而且这样的本质也不能算是 Deep Copy,因为对象里面也可能回事对象,如像下面这个状况:

varobj1 = { body: { a:10} };

varobj2 ={ body: obj1.body };

obj2.body.a=20;

console.log(obj1);

//{ body: { a: 20 } } <-- 被改到了

console.log(obj2);

//{ body: { a: 20 } }

console.log(obj1 ===obj2);

//false

console.log(obj1.body ===obj2.body);

//true

虽然obj1跟obj2是不同对象,但他们会共享同一个obj1.body,所以修改obj2.body.a时也会修改到旧的。

2、对象只有一层的话可以使用上面的:Object.assign()函数

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

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

3、转成 JSON 再转回来

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

varobj1 = { body: { a:10} };

varobj2 =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

这样做是真正的Deep Copy,这种方法简单易用。

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

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

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

varobj1 = { fun: function(){ console.log(123) } };

varobj2 =JSON.parse(JSON.stringify(obj1));

console.log(typeofobj1.fun);

//'function'

console.log(typeofobj2.fun);

//'undefined' <-- 没复制

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

4、递归拷贝

function deepClone(initalObj, finalObj) {

varobj = finalObj ||{};

for(variininitalObj) {

if(typeofinitalObj[i] ==='object') {

obj[i]= (initalObj[i].constructor === Array) ?[] : {};

arguments.callee(initalObj[i], obj[i]);

}else{

obj[i]=initalObj[i];

}

}

returnobj;

}

varstr ={};

varobj = { a: {a:"hello", b:21} };

deepClone(obj, str);

console.log(str.a);

上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。

为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。

改进版代码如下:

function deepClone(initalObj, finalObj) {

varobj = finalObj ||{};

for(variininitalObj) {

varprop = initalObj[i];//避免相互引用对象导致死循环,如initalObj.a = initalObj的情况

if(prop ===obj) {

continue;

}

if(typeofprop ==='object') {

obj[i]= (prop.constructor === Array) ?[] : {};

arguments.callee(prop, obj[i]);

}else{

obj[i]=prop;

}

}

returnobj;

}

varstr ={};

varobj = { a: {a:"hello", b:21} };

deepClone(obj, str);

console.log(str.a);

5、使用Object.create()方法

直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

function deepClone(initalObj, finalObj) {

varobj = finalObj ||{};

for(variininitalObj) {

varprop = initalObj[i];//避免相互引用对象导致死循环,如initalObj.a = initalObj的情况

if(prop ===obj) {

continue;

}

if(typeofprop ==='object') {

obj[i]= (prop.constructor === Array) ?[] : Object.create(prop);

}else{

obj[i]=prop;

}

}

returnobj;

}

6、jquery

jquery 有提供一个$.extend可以用来做 Deep Copy。

var$ = require('jquery');

varobj1 ={

a:1,

b: { f: { g:1} },

c: [1,2,3]

};

varobj2 = $.extend(true, {}, obj1);

console.log(obj1.b.f===obj2.b.f);

//false

7、lodash

另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。

var_ = require('lodash');

varobj1 ={

a:1,

b: { f: { g:1} },

c: [1,2,3]

};

varobj2 =_.cloneDeep(obj1);

console.log(obj1.b.f===obj2.b.f);

//false

这个性能还不错,使用起来也很简单。

最后,又不正之处欢迎之处

原文地址:http://www.cnblogs.com/Chen-XiaoJun/p/6217373.html

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

推荐阅读更多精彩内容

  • 深拷贝:复制多层 浅拷贝:复制一层 在JavaScript使用过程中我们经常会遇到这样的场景; 我们发现,当数组b...
    ferrint阅读 262评论 0 0
  • 对于问题: var obj1 = {name:'小明'}; var obj2 = obj1; obj2.name ...
    蓝摇扼剑阅读 334评论 0 0
  • 深复制和浅复制只针对像 Object, Array 这样的复杂对象。简单来说,浅复制只复制一层对象的属性,而深复制...
    婷楼沐熙阅读 306评论 0 0
  • 最近在研究lodash的源码,涉及到深 拷贝和浅拷贝的问题,由此,搜集网上的资料来研究了一番。 共同点: 都是在已...
    剑来___阅读 145评论 0 0
  • 在做项目的时候遇到了一个问题。对象的复制 例:object1 = object2;发现了修改object1的时候,...
    赵BW阅读 295评论 0 0