深拷贝与浅拷贝

其实都是老生常谈的问题了,经常会看见关于深拷贝与浅拷贝的话题,自己也一直是只了解一点,并未系统深入的研究过,理解也始终浅薄,想自己再整理一遍,作为自己的一次学习上的补充。

主要会从以下几个方向来编写:

  1. 什么是浅拷贝和深拷贝?

  2. 浅拷贝和深拷贝的应用场景?

  3. 如何实现?

1. 什么是浅拷贝和深拷贝?

为什么会有深拷贝和浅拷贝,这跟JS中的数据类型有关系。

在JavaScript中,数据分为基本类型和引用类型。

  1. 基本类型包括string,number,boolean,null和undefined,(ES6中还包括Symbol)。这几种基本类型的数据都是直接复制到变量中,它们按照值进行赋值、复制、传递函数参数以及返回函数结果。

  2. JS的其余部分则依赖于引用。引用时指向对象所在内存位置的指针。两个或多个变量不需要各自拥有某个对象的副本,它们只需要指向同一个对象即可。通过引用对指称目标作出的修改会反映到其他引用中。

所以,其实浅拷贝和深拷贝这两个概念是针对引用类型来说的。

而区分浅拷贝和深拷贝的前提是,对象里嵌套对象的情况,如var obj = {name: 'armor', arr: [1, 2, 3]}

如果只是纯对象的话,例如:var obj = {name: 'armor'}这样的情况,就没必要区分浅拷贝和深拷贝了。

现在再来看看浅拷贝和深拷贝,顺便可以加上赋值来对比一下:

  1. 赋值:一个对象被赋值后,两个对象指向的是同一个存储空间,无论哪个对象发生改变,都会影响另一个对象。

  2. 浅拷贝:浅拷贝只复制指向某个对象的指针,而不复制对象本身,也就是拷贝的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变。

  3. 深拷贝:开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

用两个个例子来看一下浅拷贝和深拷贝的效果,

先来看一个浅拷贝的例子:


var obj1 = {

    name: 'armor',

    age: 14,

    friends: ['ann', 'yang', 'Amy']

};

// 浅拷贝

var obj2 = Object.assign({}, obj1);

// 改变obj2的基本类型

obj2.name = 'name';

// 改变obj2中嵌套的对象

obj2.friends[1] = 'liu';

console.log('obj1: ', obj1);

console.log('obj2: ', obj2);

<!--输出结果:影响到了原数据-->

obj1:  { name: 'armor', age: 14, friends: [ 'ann', 'liu', 'Amy' ] }

obj2:  { name: 'name', age: 14, friends: [ 'ann', 'liu', 'Amy' ] }

下面这个是深拷贝:


var obj1 = {

    name: 'armor',

    age: 14,

    friends: ['ann', 'yang', 'Amy']

};

// 深拷贝

var obj2 = Object.assign({}, obj1);

// 改变obj2的基本类型

obj2.name = 'name';

// 改变obj2中嵌套的对象

obj2.friends[1] = 'liu';

console.log('obj1: ', obj1);

console.log('obj2: ', obj2);

<!--输出结果:没有影响原数据-->

obj1:  { name: 'armor', age: 14, friends: [ 'ann', 'yang', 'Amy' ] }

obj2:  { name: 'name', age: 14, friends: [ 'ann', 'liu', 'Amy' ] }

可以看见浅拷贝后的对象,修改对象中第一层为基本数据类型的值对原数据没有影响;修改原对象中的子对象,会使原数据一同改变。

而深拷贝则完全不会影响到原数据。

2. 浅拷贝和深拷贝的应用场景?

  1. 浅拷贝一般用在涉及到状态变化时使用,比如 React/Redux 和 Preact/unistore 之流。

  2. 例如一个表格中的信息,点击修改或编辑,当弹出框中的内容变化时,我们希望不会影响到表格中的数据,,而是确定修改完之后再改变表格数据。这时需要两个数据互不影响,就可以使用深拷贝。

  3. 从服务器fetch到数据之后我将其存放在store中,通过props传递给界面,然后我需要对这堆数据进行修改,那涉及到修改就一定有保存和取消,所以我们需要将这堆数据拷贝到其他地方

3. 浅拷贝的实现

3.1 用于对象:Object.assign()

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。


var obj1 = {

    people: {

        name: 'armor'

    }

};

var obj2 = Object.assign({}, obj1);

obj2.people.name = 'name';

console.log(obj1.people.name);

// 输出: name

3.2 用于数组:Array.prototype.concat()


var obj1 = [{

    arr: [1, 2, 3]

}];

var obj2 = obj1.concat();

obj2[0].arr[1] = 4;

console.log(obj1[0]);

// 输出: { arr: [ 1, 4, 3 ] }

3.3 用于数组:Array.prototype.slice()


var obj1 = [{

    arr: [1, 2, 3]

}, 2, 3];

var obj2 = obj1.slice();

obj2[0].arr[1] = 4;

obj2[1] = 5; // 修改第一层基础类型不影响原数据

console.log(obj1);

// 输出: [ { arr: [ 1, 4, 3 ] }, 2, 3 ]

3.4 手动遍历复制实现


function shallowCopy(source) {

    // 如果不是对象,返回自身

    if(!(typeof source === 'object' && source != null)) return source;

    var target = Array.isArray(source) ? [] : {}; // 判断对象是不是数组

    for(var key in source) {

        if(source.hasOwnProperty(key)) {

            target[key] = source[key];

        }

    }

    return target;

}

上面几种方式都是浅拷贝的实现,当然,如果没有嵌套的子对象存在时,其实也可以把这几个方法当作深拷贝来使用。

4. 深拷贝的实现

4.1 用于对象或数组:JSON.parse(JSON.stringify())

注意:不能用于处理函数和undefined


var obj1 = [{

    arr: [1, 2, 3]

}, 2, 3, function(){console.log(1);}, undefined];

var obj2 = JSON.parse(JSON.stringify(obj1));

obj2[0].arr[1] = 4;

obj2[1] = 5;

console.log(obj1);

console.log(obj2);

// 输出:

// [ { arr: [ 1, 2, 3 ] }, 2, 3, [Function], undefined ]

// [ { arr: [ 1, 4, 3 ] }, 5, 3, null, null ]

可以看见,深拷贝不影响原对象,但是这个方法不能对函数及undefined进行拷贝处理,有缺陷。

4.2 递归遍历

实现方式:

  • 遍历对象或数组

  • 判断对象里每一项的数据类型

  • 如果是基本类型,直接赋值,否则递归调用自身,继续判断

其实这个深拷贝只不过是在浅拷贝的基础上加上递归实现的。可以说浅拷贝就是只拷贝了一层,而深拷贝就是无限层级的拷贝。


function deepCopy(source) {

    if(!(typeof source === 'object' && source != null)) return source;  // 如果不是对象,返回自身

    var target = Array.isArray(source) ? [] : {}; // 判断对象是不是数组

    for(var key in source) {

        if(typeof source[key] === 'object' && source[key] != null) {

            target[key] = deepCopy(source[key]);

        } else {

            target[key] = source[key];

        }

    }

    return target;

}

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

推荐阅读更多精彩内容