前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >JavaScript中的浅拷贝与深拷贝

JavaScript中的浅拷贝与深拷贝

作者头像
蒋鹏飞
发布于 2020-10-15 01:55:15
发布于 2020-10-15 01:55:15
74300
代码可运行
举报
文章被收录于专栏:进击的大前端进击的大前端
运行总次数:0
代码可运行

JS中有两种数据类型,值类型和引用类型,当我们需要把一个变量赋给另一个变量时,对于值类型很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let a = 1;
let b = a;
b = 10;
console.log(a, b); // 1, 10

但是如果a是一个对象,这就有问题了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let a = {value: 1};
let b = a;
b.value = 10;
console.log(a.value, b.value); // 10, 10

我们发现改变b.value的时候,a.value的值也跟着变了,这是因为JS里面的对象是引用类型,我们在把变量a赋值给变量b的时候,赋值过去的其实是a的引用地址,b有了相同的引用地址,那a跟b指向的都是同一块内存空间,操作b的属性,其实就是操作了这块内存,因为a也指向这块内存,所以a的属性也变了。这其实就是一个浅拷贝。

浅拷贝

上面这样我们直接将一个引用变量赋值给另一个变量是一种浅拷贝,浅拷贝其实还有其他形式。这次我们需要拷贝的目标是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let target = {
    name: 'John',
  age: 20,
  friend: {
    name: 'Michel',
    age: 30
  }
}

我们可以直接遍历target对象,将它赋给一个新对象就行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const shallowCopy = (obj) => {
  // 判断参数是数组还是对象
  const result = Array.isArray(obj) ? [] : {};
  for(let key in obj) {
    // 使用hasOwnProperty来判断是否是自身属性
    // 只拷贝自身属性,不拷贝原型链上的属性,即继承属性
    if(obj.hasOwnProperty(key)){
      result[key] = obj[key];
    }
  }

  return result;
}
复制代码

然后我们来用一下这个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let newObj = shallowCopy(target);
newObj.age = 50;
console.log(target.age, newObj.age); //20, 50

我们可以看到当我们改变newObj的属性时,原对象的属性并没有受影响,但是如果我们改变newObj.friend呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
newObj.friend.age = 50;
console.log(target.friend.age, newObj.friend.age); //50, 50

我们发现当我们改变newObj.friend的属性的时候,原对象的newObj.friend的属性也改变了,这是因为target.friend本身也是一个对象,我们拷贝的时候只拷贝了他的引用地址,所以我们通过newObj操作他的时候也改变了原来的target

从上面可以看出我们的shallowCopy方法只拷贝了对象的一层,这也是一种浅拷贝。其实还有一些原生方法也是只拷贝一层的,比如Object.assign...扩展运算符

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let newObj = Object.assign({}, target); // 这是一层的浅拷贝
let newObj = {...target};  // 这也是一层的浅拷贝

那深拷贝应该怎么实现呢?

深拷贝

JSON

最简单的实现方法就是用JSON.stringify先将对象转换为字符串,然后再用JSON.parse重新解析为JSON,这样新生成的对象与原对象就完全没有关系了,还是以前面的target为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let newObj = JSON.parse(JSON.stringify(target));

newObj.friend.age = 50;
console.log(target.friend.age, newObj.friend.age); //30, 50

但是我们换一个target再来试试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let target2 = {
  name: 'John',
  age: 20,
  drive: () => {},
  girlFriend: undefined
}

let newObj = JSON.parse(JSON.stringify(target2));
console.log(newObj);
复制代码

结果如下图,我们发现drivegirlFriend两个属性都丢了,这是因为**JSON.stringify**不能将方法和**undefined**属性转化为字符串,在转换为字符串过程中就丢了,再解析回来自然也没有了

递归遍历

要解决上面的问题,我们还要自己动手,我们改造下上面的shallowCopy方法,让他能够递归复制。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const deepCopy = (obj) => {
  const result = Array.isArray(obj) ? [] : {};
  for(let key in obj) {
    if(obj.hasOwnProperty(key)){
      // 如果属性也是对象,递归调用自身
      if(obj[key] && typeof obj[key] === 'object'){
        result[key] = deepCopy(obj[key])
      } else {
        result[key] = obj[key];
      }
    }
  }

  return result;
}
复制代码

来看下结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let newObj = deepCopy(target2);
console.log(newObj);

这下我们的drive方法和girlFriend属性都复制过来了。

拷贝Symbol

那如果换一个带有Symbol属性的对象呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let target3 = {
  [Symbol('name')]: 'John',
  age: 20,
  drive: () => {},
  girlFriend: undefined
}
复制代码

我们来看看结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let newObj = deepCopy(target3);
console.log(newObj);

我们发现Symbol属性丢了,那怎么办呢?这个原因是for...in...循环拿不到Symbol属性,如果要拿Symbol属性,我们可以用Object.getOwnPropertySymbolsReflect.ownKeysObject.getOwnPropertySymbols会返回对象的Symbol属性列表:

Reflect.ownKeys会返回对象的所有自有属性,包括Symbol属性和不可枚举属性,但是不包括继承属性。所以我们的deepCopy方法改为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const deepCopy = (obj) => {
  const result = Array.isArray(obj) ? [] : {};
  // 用 Reflect.ownKeys可以获取Symbol属性,用for...of来循环数组
  for(let key of Reflect.ownKeys(obj)) {
    if(obj.hasOwnProperty(key)){
      if(obj[key] && typeof obj[key] === 'object'){
        result[key] = deepCopy(obj[key])
      } else {
        result[key] = obj[key];
      }
    }
  }

  return result;
}
复制代码

再来看看结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let newObj = deepCopy(target3);
console.log(newObj);

解决循环引用

我们来考虑一个新的目标对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let target4 = {
  [Symbol('name')]: 'John',
  age: 20,
  drive: () => {},
  girlFriend: undefined
}

target4.target = target4;
复制代码

这个对象的target属性又引用了自身,所以有了循环引用,用我们之前的深拷贝方法直接会报错

要解决这个问题,我们需要每次都将引用类型的键和值都记录下来,由于Object的键不能是对象,所以我们不能用Object记录,这里采用了WeakMap来记录:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const deepCopy2 = (originObj) => {
  // 全局只能有一个记录的map,所以里面又嵌了一个方法
  const map = new WeakMap();
  function dp(obj){
    const result = Array.isArray(obj) ? [] : {};

    const existObj = map.get(obj);
    // 检查map中是不是已经有这个对象了,有了就直接返回,不再递归
    if(existObj){
      return existObj;
    }

    // 没有就记录下来
    map.set(obj, result);

    for(let key of Reflect.ownKeys(obj)) {
      if(obj.hasOwnProperty(key)){
        if(obj[key] && typeof obj[key] === 'object'){
          result[key] = dp(obj[key])
        } else {
          result[key] = obj[key];
        }
      }
    }

    return result;
  }

  return dp(originObj);
}
复制代码

WeakMap的兼容性不是很好,如果是老浏览器不支持WeakMap,我们可以用两个数组来模拟,一个数组存键,一个数组存值,每次都只在两个数组末尾新增值,这样键和值在数组中的索引就是一样的,我们可以通过这个索引来进行键和值的匹配。

浅拷贝的应用:mixin--混合模式

直接看代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const mixin = {
  // 注意:这里的say和run不能写成箭头函数,因为箭头函数拿不到正确的this
  say() {
    console.log(`${this.name}在说话`)
  },
  run() {
    console.log(`${this.name}在跑步`)
  }
}

class Student{
  constructor(name){
    this.name = name
  }
}

Object.assign(Student.prototype, mixin);

const student1 = new Student('Jhon');
student1.say();
复制代码

上面的代码我们没有用继承,而是用了拷贝的方式,让Student类具有了mixin的方法,我们直接将mixin里面的方法复制到了Student的原型链上。这种模式在很多地方都有应用,比如Vue:

深拷贝应用:pick函数

在underscore里面有一个pick函数,可以实现如下效果:

上述代码的输出是一个只包含age属性的新对象{age: 30},下面让我们自己来实现一个pick函数,实现在原理很简单,把我们之前深拷贝的方法改一下就行,让他只拷贝我们需要的属性:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const pick = (originObj, property) => {
  const map = new WeakMap();
  function dp(obj){
    const result = Array.isArray(obj) ? [] : {};

    const existObj = map.get(obj);

    if(existObj){
      return existObj;
    }

    map.set(obj, result);

    for(let key of Reflect.ownKeys(obj)) {
      // 只需要加一个检测,看看key是不是我们需要的属性就行
      if(obj.hasOwnProperty(key) && key === property){
        if(obj[key] && typeof obj[key] === 'object'){
          result[key] = dp(obj[key])
        } else {
          result[key] = obj[key];
        }
      }
    }

    return result;
  }

  return dp(originObj);
}复制代码

原创不易,每篇文章都耗费了作者大量的时间和心血,如果本文对你有帮助,请点赞支持作者,也让更多人看到本文~~

更多文章请看我的掘金文章汇总

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JS拷贝指南:浅拷贝与深拷贝详解
在JavaScript编程中,数据的复制是一个基础而又至关重要的概念,尤其在处理复杂的数据结构时,正确地执行拷贝操作可以避免意料之外的数据修改问题。JavaScript中的数据类型分为基本类型(如number、string、boolean等)和引用类型(如object、array、function等)。基本类型的值存储的是值本身,而引用类型存储的是指向该数据在内存中位置的引用。因此,对于引用类型而言,拷贝操作分为浅拷贝(Shallow Copy)和深拷贝(Deep Copy)两种情况。 浅拷贝:表面的复制 浅拷贝创建一个新的对象或数组,但它仅复制第一层的元素或属性,如果这些元素或属性是引用类型,则新旧对象将共享同一份引用。这意味着对拷贝后对象中引用类型属性的修改会影响到原对象。以下是一些实现浅拷贝的方法:
FGGIT
2024/11/19
4090
大全!JavaScript中深浅拷贝内部方法与手写函数
怎么理解这句话:浅拷贝过程实质上是创建了一个新的变量,但这个新变量与原变量指向同一个内存地址上的对象。这意味着原对象和拷贝对象共享相同的数据结构和内部状态。因此,对拷贝对象所做的任何修改,如果涉及到修改共享的数据结构,也会影响到原始对象。同样的,原始对象所做的任何修改,如果涉及到修改共享的数据结构,也会影响到拷贝对象。
用户6256742
2024/08/13
1580
JS 深拷贝与浅拷贝
其实在工作写代码和面试中,会经常碰到这两个概念:深拷贝,浅拷贝。但今天的重点是深拷贝。
Umbrella1024
2021/02/18
2.1K0
什么是深拷贝;深拷贝和浅拷贝有什么区别;深拷贝和浅拷贝有哪些方法(详解)
在JavaScript中,对象和数组是引用类型,这意味着当你将它们赋值给一个变量或者作为函数参数传递时,你实际上是在传递一个指向内存中对象的引用,而不是对象本身的副本。这就涉及到了深拷贝和浅拷贝的概念。
watermelo37
2025/01/22
1790
什么是深拷贝;深拷贝和浅拷贝有什么区别;深拷贝和浅拷贝有哪些方法(详解)
高频js手写题之实现数组扁平化、深拷贝、总线模式
古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。看懂一道算法题很快,但我们必须将这道题的思路理清、手写出来。
helloworld1024
2022/10/06
3750
JS手撕(二) 数组扁平化、浅拷贝、深拷贝
数组扁平化就是将多层数组拍平成一层,如[1, [2, [3, 4]]]变成[1, 2, 3, 4]
赤蓝紫
2023/01/01
1.3K0
《现代Javascript高级教程》JavaScript深拷贝与浅拷贝
在JavaScript中,对象的拷贝是一项常见的操作。浅拷贝和深拷贝是两种常用的拷贝方式。浅拷贝只复制对象的引用,而深拷贝创建了一个全新的对象,包含与原始对象相同的值和结构。深拷贝和浅拷贝各有适用的场景和注意事项。本文将详细介绍如何实现一个完整而优雅的深拷贝函数,处理循环引用和特殊类型,优化性能,并探讨深拷贝和浅拷贝的应用场景、注意事项和相关属性。
linwu
2023/07/27
6310
JavaScript专题之深浅拷贝
如果是数组,我们可以利用数组的一些方法比如:slice、concat 返回一个新数组的特性来实现拷贝。
疯狂的技术宅
2019/03/28
4130
JavaScript专题之深浅拷贝
一篇文章彻底搞懂浅拷贝和深拷贝的区别_深拷贝和浅拷贝的题
由博主《前端初级工程师面试系列一JS基础》文章一JS变量类型引伸的考点,变量类型分为基本类型和引用类型,那么在变量拷贝赋值时,也是不一样的,分为浅拷贝和深拷贝,是面试中常考的知识点,也是实际开发中经常会用到的内容。
全栈程序员站长
2022/11/10
4920
一篇文章彻底搞懂浅拷贝和深拷贝的区别_深拷贝和浅拷贝的题
谈谈深拷贝和浅拷贝的区别
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
终码一生
2022/04/15
4060
谈谈深拷贝和浅拷贝的区别
javascript对深拷贝对象的研坑
对于深拷贝,浅拷贝的概念不多说,概念可以自行百度哟!这里对深拷贝对象进行一些研究!
张炳
2019/08/02
4570
实现浅拷贝与深拷贝
Js包含基本数据类型与引用数据类型两种不同的数据类型的值,深拷贝与浅拷贝的概念只存在于引用数据类型。对于引用类型,浅拷贝是拷贝了指向这个对象堆内存的指针,是拷贝了对原对象引用,深拷贝是拷贝了该对象的所有属性到一个新的对象,若是原对象的某属性依然引用了其他对象,那么需要将原对象引用的其他对象一并进行深拷贝,并不断递归进行。对于基本数据类型是不存在深拷贝与浅拷贝的概念的,如果将一个基本数据类型变量的值赋值到另一个变量,那么新变量的值是对于原变量值的复制而不是引用,如果必须要按照深浅拷贝的概念理解的话,对于基本数据类型的复制可以理解为按值深拷贝。
WindRunnerMax
2020/08/27
6410
面试官想要的 JS 基本类型
面试的时候我们经常会被问答js的数据类型。大部分情况我们会这样回答包括: 基本类型(值类型或者原始类型): Number、Boolean、String、NULL、Undefined以及ES6的Symb
grain先森
2019/04/17
5780
面试官想要的 JS 基本类型
更简洁的深拷贝实现思路
深度克隆(深拷贝)一直都是初、中级前端面试中经常被问到的题目,网上介绍的实现方式也都各有千秋,大体可以概括为三种方式:
前端达人
2022/04/18
6460
更简洁的深拷贝实现思路
深浅拷贝
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
hss
2022/02/25
3010
【面试题解】JavaScript的深浅拷贝,如何手写深拷贝?
本系列面试题旨在学会相关知识点,从而轻松应对面试题的各种形式,本文讲解了 JavaScript 中拷贝的相关知识,以及如何手写深浅拷贝。
一尾流莺
2022/12/10
4350
【面试题解】JavaScript的深浅拷贝,如何手写深拷贝?
分享 4 种 JS 深拷贝的方法
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的是内存地址 。
前端达人
2022/06/09
11.7K0
完美解决JavaScript的深浅拷贝
"拷贝"一直都是面试的热门考题。看似简单,实则难住不少面试者,回答的马马虎虎,模棱两可。抽出时间好好分析总结一下"拷贝",让这个难题彻底消失。
小丑同学
2020/09/20
5020
深入理解JavaScript中的堆与栈 、浅拷贝与深拷贝
学了这么长时间的JavaScript想必大家对浅拷贝和深拷贝还不太熟悉吧,今天在项目中既然用到了,早晚也要理清一下思路了,在了解之前,我们还是先从JavaScript的数据类型存放的位置 堆栈开始说起吧!
青梅煮码
2023/03/02
2420
js深拷贝和浅拷贝具体使用区别_es6深拷贝和浅拷贝
对于这个问题,可以考虑从深拷贝和浅拷贝的使用或者起源说起,也就是为什么会出现这个问题。
全栈程序员站长
2022/08/04
6570
推荐阅读
相关推荐
JS拷贝指南:浅拷贝与深拷贝详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文