js深拷贝浅拷贝与lodash

news2025/2/24 5:12:31

title: js深拷贝浅拷贝与lodash date: 2022/9/27 14:46:25 categories:

  • lodash
  • js入门

深拷贝和浅拷贝

ref:JS传递参数时的内部存储逻辑

JS 變數傳遞探討:pass by value 、 pass by reference 還是 pass by sharing?

在这个问题前,有另一个问题就是Array类型的拷贝。

怎么样进行Array的深拷贝

这个问题是我在leetcode时候发现的,其实就在于,当我实现迭代的时候,函数需要改变参数目的地址的实际内容,这时候就会在想对于Array的储存结构是怎么样的,修改函数内部是否会一同影响外部的变量呢,还是说只是单纯地操作到值上,并没有对初始的内存地址进行变更。

ref: https://dev.to/samanthaming/how-to-deep-clone-an-array-in-javascript-3cig

首先对于 array,显然是浅拷贝

let array = [1, 2, 3]
let arr = array

arr[0] = 1999

console.log(array) //[1999, 2, 3]

可以使用解析式一种方式去clone数组

let array = [1, 2, 3]
let arr = [...array]

arr[0] = 1999

console.log(array) //[1, 2, 3]

而这种方法会在嵌套数组当中不起效

let nestedArray = [1, [2], 3];
let arrayCopy = [...nestedArray]; 

// Make some changes
arrayCopy[0] = '👻'; // change shallow element
arrayCopy[1][0] = '💩'; // change nested element
console.log(arrayCopy); // [ '👻', [ '💩' ], 3 ]

// ❌ Nested array got affected
console.log(nestedArray); // [ 1, [ '💩' ], 3 ]

当我们改动嵌套数组的时候,源数组也会收到affacted。因此我们可以通过这样的方式对数组进行深拷贝

let nestedArray = [1, [2], 3];
let arrayCopy = JSON.parse(JSON.stringify(nestedArray)); 

// Make some changes
arrayCopy[0] = '👻'; // change shallow element
arrayCopy[1][0] = '💩'; // change nested element
console.log(arrayCopy); // [ '👻', [ '💩' ], 3 ]

// ✅ Nested array NOT affected
console.log(nestedArray); //  1, [ 2 ], 3 ]

但是 JSON.parse(JSON.stringify(nestedArray))

这样的方法并不是万能的,会导致这样的问题。比如说undefined类型会转化为null,DOM类型会转化成空对象,而Date类型会转化为String。

function nestedCopy(array) {
    return JSON.parse(JSON.stringify(array));
}

// undefineds are converted to nulls
nestedCopy([1, undefined, 2]) // -> [1, null, 2]

// DOM nodes are converted to empty objects
nestedCopy([document.body, document.querySelector('p')]) // -> [{}, {}]

// JS dates are converted to strings
nestedCopy([new Date()]) // -> ["2019-03-04T10:09:00.419Z"]》

JSON.stringify/parse only work with Number and String and Object literal without function or Symbol properties.

因此这个时候可以引入lodash,帮助我们去解决这个问题

const lodashClonedeep = require("lodash.clonedeep");

const arrOfFunction = [() => 2, {
    test: () => 3,
}, Symbol('4')];

// deepClone copy by refence function and Symbol
console.log(lodashClonedeep(arrOfFunction));
// JSON replace function with null and function in object with undefined
console.log(JSON.parse(JSON.stringify(arrOfFunction)));

// function and symbol are copied by reference in deepClone
console.log(lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]);
console.log(lodashClonedeep(arrOfFunction)[2] === lodashClonedeep(arrOfFunction)[2]);

当然,还有另一种方法则是嵌套拷贝


function deepCopy(obj) {
  var newobj = obj.constructor === Array ? [] : {};
  if (typeof obj !== 'object') {
    return obj;
  } else {
  for (var i in obj) {
    if (typeof obj[i] === 'object'){ //判断对象的这条属性是否为对象
      newobj[i] = deepCopy(obj[i]);  //若是对象进行嵌套调用
    }else{
        newobj[i] = obj[i];
        }
    }
    }
    return newobj; //返回深度克隆后的对象
}

var obj1 = {
    name: 'shen',
    show: function (argument) {
        console.log(1)
    }
}
var obj2 = deepCopy(obj1)

如果对于js的深浅拷贝该怎么解释呢

阅览,我十分赞同这么一个说法:不论是传引用还是传值,定义都可以归结为 传值

即: pass by value,为什么可以这么归结呢?

原始数据类型传递

对于js在储存结构里不变的Number、String等,在传参数的时候,属于之间传递值。

function test(primitiveData) {
  primitiveData = primitiveData + 5;
  console.log(primitiveData); // 10
}

let a = 5; // Primitive type data
test(a);

console.log(a); // 5 => 沒被改變

这个过程的执行顺序为

  1. 声明function test()
  2. 将数值5赋给primitiveData
  3. 将primitiveData累加5
  4. 因为primitiveData相当于一个新的对象,因此不会修改原来a的值

Untitled

引用数据类型传递

而对于引用类型,如Object、Array等,可以视作为传递引用。因为对于非原始数据类型,引用变量储存的数据是真实数据所在的地址,其所经历过的过程为。


function test(objectData) {
  objectData.number = 10; // 改變物件內容
  console.log(objectData); // { number: 10 }
}

let a = { number: 5 }; // Object data
test(a);

console.log(a); // { number: 10 } => 跟著改變
  1. 声明function test()
  2. 将对象a的数值地址传递objectData
  3. 修改对应数值地址的变量属性number为10
  4. 源对象a的数值同样也被修改

Untitled

拷贝过程的内存地址走向

为什么会有这样的问题呢?因为js在储存数据的时候,对于原始数据类型跟引用类型方式稍有不同。

在原始数据类型当中,这些value会存在栈中。因此,在使用=进行赋值的时候,会直接将栈区的数据复制一份。而对于引用类型,栈区则是会存储其处于堆区,因此在传值的时候,则是会把堆区这一块数据地址传递进去。

所以,这也是为什么说将Object引用类型传递会与源数据共享一个地址。

三点(…)运算符 & Object.assign

有很多方式可以去解决深拷贝的问题,最简单的可以使用JSON库的序列化和反序列进行,不过这种方式会对比如说DOM类型,undefined类型不友好。

而对于只有一级属性的时候,则是可以通过…运算符或者 Object.assign()

const foo = {
    number : 1
}

const foo2 = Object.assign({}, foo)
const foo3 = {...foo}
foo2.number = 10
foo3.number = 20

console.log(foo3) //{number: 20}
console.log(foo2) //{number: 10}
console.log(foo)  //{number: 1}

总结

我的想法是,对于JS而言,pass by reference和pass by value都可以视为pass by value,因为在进行赋值操作的时候,JS都会复制一份栈区的数据。而对于Object类型栈区所存数据为「address地址」,而对于Primitive Type而言,则是直接存入原始数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/76493.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode HOT 100 —— 169.多数元素

题目 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 思路 方法一:哈希表 遍历整个数组,记录每个数值出…

JAVA IO详解

目录 一、流的概念与作用 二、Java IO的用途和特征 三、流的使用详解 一、流的概念与作用 流(Stream): 在Java IO中,流是一个核心的概念。流从概念上来说是一个连续的数据传输过程。人们根据数据传输特性将流抽象为各种类,方便更直观的进…

【服务器数据恢复】服务器raid5崩溃导致上层应用不可用的数据恢复案例

服务器数据故障: 某公司服务器8块硬盘组成raid5磁盘阵列,其中有2块硬盘故障指示灯报警,其他硬盘指示灯正常,上层应用不可用。 服务器数据恢复过程: 1、服务器数据恢复工程师拿到故障服务器所有硬盘后对出现物理故障的…

ADI Blackfin DSP处理器-BF533的开发详解18:用触摸屏的例程来理解中断(含源码)

硬件准备 ADSP-EDU-BF533:BF533开发板 AD-HP530ICE:ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 硬件实现原理 ADSP-EDU-BF533 开发板上的中断资源连接到了 CPLD,并通过 CPLD 将中断信号连接到 PF0 触发,通过 CPLD映射的…

ubuntu交叉编译(armv7_32位)onnx源码_cpu版本

1 下载onnx git clone https://github.com/microsoft/onnxruntime cd onnxruntime git submodule update --init --recursive2 编译 由于是交叉编译,所以需要设置一下编译工具,在网上搜索看到了这个 chineseocr_lite/build-onnxruntime-android.sh a…

Jmeter(一):jmeter概述与工作原理,安装与基本配置介绍

Jmeter(1):jmeter概述与工作原理 jmeter概述与工作原理 JMeter 是 Apache 基金会 Jakarta 上的一个纯 Java 开源项目,起初用于基 于 Web 的压力测试(pressure test),后来其应用范围逐渐扩展到对文件传输 FTP, 大型数据…

基于JavaWeb+JSP的校园二手交易平台(源码+数据库+说明文档)

目录 一、前后端功能模块 1.用户web前端页面功能模块 2.后台信息管理模块 二、开发环境 三、开发技术 四、页面设计 1.登录注册界面 2.网页主页界面 3.商品列表界面 4.商品详情界面 5.支付页面 6.支付成功后页面 7.我的订单页面 ​8.个人已发布与待处理订单界面…

google外链重要性高吗?谷歌外链作用大不大

google外链重要性高吗? 答案是:非常重要,而且要注重建设付费GPB外链。 要相信有价值的外链一般都比较难获取,那种高流量的外链一般要靠自己去outreach, 但是成功率比较低,我们需要用金钱和优质外链资源去交换 做高质…

程序员的浪费,Python一对一还原《点燃我,温暖你》里面比较火的那个爱心代码 | 附源码

前言 包子们,上午好 最近有个剧挺火的 就是那个程序员的剧,叫《点燃我,温暖你》 最近听说很火呀,那作为程序员,Python中的战斗机的小编,能不给大家安排一波! 怎么说呢,用这个表白也…

我凭借这 1000 道 java 真题,顺利拿下京东、饿了么、阿里大厂 offer

今天这篇文章也算是一次面试总结了吧! 毕竟金九银十过去了,总得给大家来点东西交代交代! 所以今天,这篇文章就应运而生了,给大家来点正正经经的干货教学,让大家体验一下干货的魅力! 小编今天这里…

【C语言数据结构(基础篇)】第一站:时间复杂度与空间复杂度

目录 一、什么是时间复杂度和空间复杂度 1.算法效率 2.时间复杂度的概念 3.空间复杂度的概念 二、如何计算常见的时间复杂度 1.大O的渐进表示法 2.一些时间复杂度的例子 (1)例1 (2)例2 (3)例3 (4)例4 (5&a…

【计算机视觉+自动驾驶】二、多任务深度学习网络并联式、级联式构建详细讲解(图像解释 超详细必看)

觉得有帮助麻烦点赞关注收藏~~~ 一、多任务网络的主要分类 目前建立的多任务网络可以分为两种方法,一种为并联多任务网络结构,另一种为级联多任务网络结构,两种网络构建方式分别如下图所示 并联式 级联式 并联网络结构大多为共享基础网络而…

ADI Blackfin DSP处理器-BF533的开发详解14:LED跑马灯(含源代码)

接口讲完了,下面写点应用程序,GPIO最典型的应用,LED跑马灯。 硬件准备 ADSP-EDU-BF533:BF533开发板 AD-HP530ICE:ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 *硬件实现原理 ADSP-EDU-BF533开发板上共设计了…

2005-2020年全国31省劳动力市场分割指数

2005-2020年全国31省劳动力市场分割指数 1、时间:2005-2020年 2、范围:包括全国31省, 3、数据内容:数据存在缺失,下载链接界面有数据预览,具体缺失情况参看链接内数据预览, 内含原始数据、A…

把废旧监控改无人机遥控车红外远程摄像头

像我们这些精打细算的业余玩家,淘个新宝贝都要掂量掂量。很羡慕能买到专用红外摄像头配无人机。可是手头不宽裕,只有一些旧零件。这都是废物再利用,所以说不要太追求性能了,自然让他工作就好,测试这条路线的可行性。 …

blneder 蜡笔

文章目录简介.打开蜡笔.基本操作.自由线.图形工具.图层.遮罩.画布.画布原点.![在这里插入图片描述](https://img-blog.csdnimg.cn/46cb7019e8ff41e6b391e056c616ce32.png)画布旋转.辅助.圆形.径向.平行.栅格.等距.编辑模式.顶部工具栏.选择.曲线编辑.左侧工具栏.快捷键.画笔深度…

值得一看的Linux内核—中断下半部之软中断

软中断 软中断(softirq)是中断处理程序在开启中断的情况下执行的部分,可以被硬中断抢占。 内核定义了一张软中断向量表,每种软中断有一个唯一的编号,对应一个softirq_action实例,softirq_action实例的成员…

b站黑马JavaScript的Node.js案例代码——考试成绩整理案例

目录 目标效果: 重点原理: 1.js中split方法——转换字符串为数组 2.js中forEach方法——遍历数组中每个对象 3.js数组操作中push方法——添加1/多个元素去数组的末尾 4.js数组操作中replace方法——在字符串中用一些字符替换另一些字符 5.js数组操…

ATtiny13与Proteus仿真-8位通用定时器/计数器与PWM仿真

8位通用定时器/计数器与PWM 1、8位通用定时器介绍 ATtiny13的8位通用定时器/计数器有两个独立的输出比较单元,并支持PWM。这意味着,可以通过8位通用定时器/计数器生产PWM信号。关于PWM的介绍,在这里就展开介绍,请参考相关资料。 ATtiny13的8位通用定时器/计数器具有如下…

备战一年,终于斩获腾讯T3,老子一定有美好的未来...

我就是那个从25岁躺平,30岁开始醒悟的“中年秃头大叔”,这人一到了中年,思考问题的方向确实不一样了。以前我不想结婚不想养育后代,天天公司摸鱼,总觉得自己赚钱自己花就挺好,25岁赚一万,30岁还…