JS深浅拷贝

简介: JavaScript中的深浅拷贝

两者区别如下:

深拷贝和浅拷贝是针对对象属性为对象的,因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这个时候你修改两者中的任何一个的值都不会影响另一个,而对于对象或者引用数据来说在进行浅拷贝时,只是将对象的引用复制了一份,也就内存地址,即两个不同的变量指向了同一个内存地址,那么在改变任一个变量的值都是该变这个内存地址的所存储的值,所以两个变量的值都会改变。

一、浅拷贝

  1. 浅拷贝:对于基本类型数据来说,拷贝的是值;对于引用数据来说,拷贝了地址,因此拷贝后的对象和原对象会共用一个内存地址,因此,属性值会同步变化(第二层往后的属性)
  2. 浅拷贝的方法:手写浅拷贝、Object.assign()、拓展运算符

1、手写一个浅拷贝

  1. 原始对象的一级属性不会被修改,但是二级往后的属性会被浅拷贝修改
function shallowClone(o) {
  let newObj = {};
  for (let key in o) {
    newObj[key] = o[key];
  }
  return newObj;
}
let obj = {
  id:'007',
  name:'xxx',
  hobby:['eat','sleep','play games'],
  foods:{
    breakfast:'milk',
    lunch:'rice',
    dinner:'fish'
  }
}
let shallowObj = shallowClone(obj);
shallowObj.id = '110'; //此时原始obj的id不会被改变,因为id是一级属性
shallowObj.hobby[2] = 'study'; //原始obj的hobby中第三个会被改变,因为属于二级属性
shallowObj.foods.dinner = 'apple'; //原始obj的foods中的dinner也会被改变
console.log(shallowObj);
console.log(obj);

结果如下:

2、Object.assign()实现浅拷贝

当对象中只有一层时,使用Object.assign()可以深拷贝。

let obj = {
  id:'007',
  name:'xxx',
  hobby:['eat','sleep','play games'],
  foods:{
    breakfast:'milk',
    lunch:'rice',
    dinner:'fish'
  }
}
let newObj = Object.assign({},obj);
newObj.name = 'xyz'; //不会改变原始对象
newObj.foods.lunch = 'none';  //会改变
console.log(obj);
console.log(newObj);

结果如下:

3、展开运算符…实现浅拷贝

数组使用拓展运算符实现浅拷贝时,第一层的属性不会改变即newObj[0] = ‘drink’ 不会改变原始数组了

let obj = {
  id:'007',
  name:'xxx',
  hobby:['eat','sleep','play games'],
  foods:{
    breakfast:'milk',
    lunch:'rice',
    dinner:'fish'
  }
}
let newObj = {...obj };
newObj.id = '120'; //不会改变原始对象
newObj.hobby[0] = 'drink'; //会改变原始对象
console.log(newObj);
console.log(obj);

结果为:

二、浅拷贝

对于引用数据类型来说,就是拷贝原始对象的所有属性与方法,在内存中重新开辟一块内存空间进行存储

1、手写一个深拷贝

(想直接看深拷贝的可以跳过,这里写出来是为了说明当属性值为数组时,直接递归已经不合适了)
这里:对于对象中属性值为引用数据类型的(数组,对象),采用递归深拷贝的方法。
但是这种方法对于对象中属性值为数组的对象来说,不好用

let obj = {
    id: '100',
    name: '小栗旬',
    hobby: ['drink', 'eat', 'play games'],
    address: {
        city: '北京市',
        area: '海淀区'
    }
}
// 手写一个深拷贝
function deepClone(obj) {
  let newObj = {};
    for (let key in obj) {
        // 如果是引用数据类型,就递归深拷贝
        if (typeof obj[key] === 'object') {
            newObj[key] = deepClone(obj[key]);
        } else {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}
let deepObj = deepClone(obj);
deepObj.id = '10086'; //完全不影响原始对象
deepObj.name = '张学友';
deepObj.hobby[0] = 'study'; //完全不影响
deepObj.address.city = '上海市'; //完全不影响
deepObj.address.area = '静安区'; //完全不影响
console.log(deepObj);
console.log(obj);

对于obj中的hobby来说,不好用,结果为:

完整版深拷贝:对于属性值为数组等非键值对的深拷贝,需要先判断是否为数组

function deepClone(obj) {
  // 如果传入的是基本类型,则直接返回
  if(typeof obj !=='object') {
    return obj;
  }
  let newObj = Array.isArray(obj)?[]:{};
  for(let key in obj) {
    //保证key不是Object原型上的属性,而是属于obj自身
    if(obj.hasOwnProperty(key)) {
      //如果obj[key]还是对象,则递归,否则(是数组或基本类型)复制
      if(obj[key] && typeof obj[key] === 'object') {
        newObj[key] = deepClone(obj[key]);
      }else{
        newObj[key] = obj[key];
      }
    }
  }
  return newObj;
}
let origin1 = null;
let origin2 = {
  id:'008',
  name:'LULULU',
  hobby:['吃饭','睡觉','打游戏'],
  address:{
    city:'北京市',
    area:'朝阳区'
  }
}
let deepObj1 = deepClone(origin1);
console.log(origin1,deeoObj1); //当拷贝的原始对象是null时,会返回null的
let deepObj2 = deepClone(origin2);
deepObj2.id = '66666';
deepObj2.name = 'MARK';
deepObj2.hobby[2] = '学习';
deepObj2.address.city = '上海市';
deepObj2.address.area = '静安区';
console.log(deepObj2);
console.log(origin2);

当拷贝空对象时,也会返回空对象,当属性值为数组时,也会进行深拷贝,完全符合要求
结果为:

2、JSON.parse(JSON.stringify(obj))

使用JSON.stringify(obj)将对象转化为字符串,再使用JSON.parse()将字符串转化为JSON对象。

let obj = {
  id:'008',
  name:'LULULU',
  hobby:['吃饭','睡觉','打游戏'],
  address:{
    city:'北京市',
    area:'朝阳区'
  }
}
let newObj = JSON.parse(JSON.stringify(obj));
newObj.name = 'kakaka';
newObj.hobby[0] = '逛街';
newObj.address.area = '海淀区';
console.log(newObj);
console.log(obj);

结果为:

3、函数库lodash提供的cloneDeep函数

npm i lodash 安装依赖
import _ from 'lodash' 导入依赖
var newObj = _.cloneDeep(obj); 使用深拷贝
目录
相关文章
|
JavaScript 前端开发
vue前端下载,实现点击按钮弹出本地窗口,选择自定义保存路径
这个不用代码实现(网上也找不到方法可以调出另存为窗口),更改浏览器设置就可以,否则,现在的浏览器都是默认直接保存到下载路径中
1543 3
|
Java API 开发工具
如何用阿里云 oss 下载文件
阿里云对象存储服务(OSS)提供了多种方式下载文件,以下讲解下各种方式的下载方法
11236 2
|
Java 微服务 Spring
@EnableDiscoveryClient注解的作用
@EnableDiscoveryClient注解的作用 @EnableDiscoveryClient 及@EnableEurekaClient 类似,都是将一个微服务注册到Eureka Server(或其他 服务发现组件,例如Zookeeper、Consul等)
2040 0
|
JSON 前端开发 Go
前端文件下载的方式
【10月更文挑战第5天】
409 58
|
资源调度 JavaScript API
vue-element-admin 综合开发五:引入 echarts,封装echarts 组件
这篇文章介绍了如何在vue-element-admin项目中引入并封装ECharts组件,以及如何实现折线图、柱状图和饼图的展示。
1482 4
vue-element-admin 综合开发五:引入 echarts,封装echarts 组件
|
Web App开发 JavaScript 数据可视化
vue3扩展echart封装为组件库-快速复用
vue3扩展echart封装为组件库-快速复用
779 7
|
Web App开发 移动开发 前端开发
uniapp环境H5运行及发行(入门必学)
uniapp环境H5运行及发行(入门必学)
1636 5
ElementUI表单校验trigger设为change无效问题
ElementUI表单校验trigger设为change无效问题
764 1
|
JavaScript 开发者
vue解决报错Unable to preventDefault inside passive event listener invocation.
vue解决报错Unable to preventDefault inside passive event listener invocation.
2272 0
|
存储 Java 开发者
使用Spring Boot 3.3全新特性CDS,启动速度狂飙100%!
【8月更文挑战第30天】在快速迭代的软件开发周期中,应用的启动速度是开发者不可忽视的一个重要指标。它不仅影响着开发效率,还直接关系到用户体验。随着Spring Boot 3.3的发布,其中引入的Class Data Sharing(CDS)技术为应用的启动速度带来了革命性的提升。本文将围绕这一全新特性,深入探讨其原理、使用方法以及带来的实际效益,为开发者们带来一场技术盛宴。
986 2