深拷贝

来自 # js 什么是深拷贝问题?

在 JavaScript 中,数据类型包括:

1、基本数据(值)类型,如 undefined、null、number、boolean、string等

2、引用类型:如Object、Array、Function等。

一、什么是值类型?

var a = 1;
var b = a;

b = b + 1
console.log(a, b) // 1, 2

值类型赋值时,会在栈内存空间中分配全新的地址,并得到相应的值。代码中 b 是从 a 创建来的,当b 变化时,a 不会受到影响。这是符合常识的。

二、什么是引用类型?

var data =  {
    id: 0,
    book: 'learn redux',
    avaliable: false
}

var newData = data

newData.id = 1;
console.log(data.id, newData.id) // 1 1

再来看看引用类型(以Object对象为例),对象实际是存在于堆内存空间中。当我们要访问一个对象的时候,实际上是从栈内存中获取引用地址,然后根据这个引用地址再从堆内存中获取所需要的值。

而引用类型赋值时,实际上是获取其引用地址,而不是直接获取值。所以,值发生改变时,源对象也会随着改变。

三、使用ES Next新特性带来的 Object.assign 方法 和 扩展运算符

1.对象

使用 Object.assign({}, ...)来解决 "单层"对象引用问题

let data =  {
    id: 0,
    book: 'learn redux',
    avaliable: false
}

let newData = Object.assign({}, data)

newData.id = 1;
console.log(data.id, newData.id) // 0 1

或者使用对象的扩展运算符

var data =  {
    id: 0,
    book: 'learn redux',
    avaliable: false
}
var newData = {title: 'fuck', ...data};
newData.id = 1;
console.log(data.id, newData.id) // 0 1
2.数组

(PS:JavaScript的数组也是引用类型,同理可以使用 Array.concat 方法 和 数组的扩展运算符来解决引用问题,这里就省略了)

四、Object.assign 方法 和 扩展运算符的 “深入浅出” 问题 —— 浅拷贝

需要注意的是,使用 Object.assign 及 扩展运算符都属于浅操作。如果往对象的外面再套一层的话,那问题就会浮现出来了。

var data =  {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false    
    }
}
var newData = Object.assign({}, data)
newData.item.id = 1;
console.log(data.item.id, newData.item.id) // 1 1

上述的结果表明,原始数据还是被更改了。

五、解决深拷贝问题常见的三种方法

1、jQuery的深拷贝工具: $.extend(true, ...)

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
// 首参必须设置为 true 才算是深拷贝
let newData = $.extend(true, {}, data);
newData.item.id = 1
console.log(data.item.id, newData.item.id) // 0 1

为什么$.extend默认不使用深拷贝,还需要手动传参true才开启呢?

这是因为深拷贝在实战中是比较消耗性能的,如无必要请使用浅拷贝即可。稍后我们会实现一个自己的深拷贝工具。你可以从中了解详情。

2、巧用序列化:JSON.parse(JSON.stringify(...))

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
let newData =  JSON.parse(JSON.stringify(data))
newData.item.id = 1
console.log(data.item.id, newData.item.id) // 0 1

原理很简单,就是先将对象转换为字符串,再重新序列化为对象,这样理所当然不会有引用问题。值得一提的是,这些操作对数组也是有效的。

3、递归拷贝(深拷贝)

在我们封装一个自己的深拷贝工具之前,我们需要先跳过一些可能的拷贝错误认知 —— “不要单纯的认为,只要是拷贝嵌套对象就会产生深拷贝问题”。

(1)就算是拷贝嵌套对象,如果操作的只是基本数据类型,不会有影响的。

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
let newData = Object.assign({}, data);
newData.title = 'fuck'
console.log(data.title, newData.title) // 123  fuck

(2)就算是嵌套对象,如果只拷贝其中一层对象,是不会产生问题的。

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
let newData = Object.assign({}, data.item);
newData.id = 1;
console.log(data.item.id, newData.id) // 0 1

有了以上两个认知,实际上我们可以实现手动深拷贝了。

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}

// 创建一个空对象
let newData = {}

// 基本数据类型,可以直接拿
newData.title = data.title

// data.item对象,其实可以使用 Object.assign 来拿
// newData.item = Object.assign({}, data.item)

// 但为了体现手动深拷贝细节,还是一步一步来吧。
// 通过对data.item拆解,item对象被转换为一个个的基本数据类型直接赋值给newData.item的每一项
// 如果 item 还有对象的话,那就只能依样画葫芦继续拆解。直到变成基本数据类型为止。
newData.item = {}
newData.item.id = data.item.id
newData.item.book = data.item.book
newData.item.avaliable = data.item.avaliable

newData.item.id = 1
console.log(data.item.id, newData.item.id) // 0 1

通过对data.item拆解,item对象就转换为一个个的基本数据类型了,所以可以直接赋值给newData.item的每一项了。

如果 item 还有对象的话,那就只能依样画葫芦继续拆解。直到变成基本数据类型为止。

知道了如何手动深拷贝,现在我们把他转换为自动化深拷贝,常见的的实现方式是递归拷贝了:

// 递归深拷贝
var deepExtend = function(out) {
  out = out || {};

  for (var i = 1; i < arguments.length; i++) {
    var obj = arguments[i];

    if (!obj)
      continue;

    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'object')
          out[key] = deepExtend(out[key], obj[key]);
        else
          out[key] = obj[key];
      }
    }
  }

  return out;
};

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

推荐阅读更多精彩内容

  • 写在前面 各类技术论坛关于深拷贝的博客有很多,有些写的也比我好,那为什么我还要坚持写这篇博客呢,之前看到的一篇博客...
    心_c2a2阅读 21,069评论 3 18
  • 浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用(堆和栈的关系,简单类型Undefined...
    却忘不掉你心言阅读 453评论 0 0
  • 浅拷贝与深拷贝的理解 对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去...
    喵喵喵不吃小鱼干阅读 325评论 0 1
  • 红色的葡萄酒 散发着诱人的光泽 弹奏着华丽的乐谱 音符温润如酒 我手持利刃 等待这一切的终结 锐利的双眼 如鹰眸般...
    让比耶Fonsi阅读 163评论 0 1
  • 关于茨木童子,也是有着众多的有趣传说,在著名的谣曲《罗生门》和歌舞伎《戻桥》中 ,它都被称为「罗生门之鬼」。当然,...
    流浪的阿鬼阅读 537评论 0 0