JS手写浅拷贝与深拷贝

目录

1、引言

2、深拷贝与浅拷贝介绍

2.1、概念

2.2、实现方式

3、手写代码


1、引言

要了解浅拷贝与深拷贝,首先要知道 的概念

堆栈: 就是存放数据的地方(不管是定义的数字、字符串、对象还是数组、函数等等,都会在堆或栈中开辟内存空间存放该变量。 而保存在栈内存的必须是大小固定的数据引用类型的大小不固定,所以只能保存在堆内存中

基本数据类型(存放在栈中):number、string、boolean、null、undefined

复杂数据类型(存放在堆中):对象、数组、函数

 

如上图所示,内存空间中存在内存栈和内存堆。如果是基本类型,直接存储它们的值,而如果是引用类型,会在内存堆中开辟空间进行存储,而栈中变量值其实是堆的地址,指向堆中的这个数据

JS的变量类型分为 基本类型引用类型

而深拷贝与浅拷贝的区别,其实主要是针对于 引用类型的

对于基本类型的复制,只是对它们的值进行拷贝

 

2、深拷贝与浅拷贝介绍

2.1、概念

浅拷贝: 创建一个新对象,这个新对象有着原始对象属性值的一份拷贝。

  • 如果属性是基本类型,拷贝的就是基本类型的
  • 如果属性是引用类型,拷贝的就是内存地址
  • 拷贝时,开辟一块新的内存,将原始的属性的值(基本数据类型)或地址(引用数据类型)拷贝到新开辟的内存。
  • 浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象
     

深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,新旧对象不共享内存,且修改新对象不会影响原对象

 

 对于基本类型a和引用类型c,深拷贝为a1和c1,其中c1是在堆中额外开辟新空间,并完整的拷贝了c这个数组。 这时候修改c, 是不会影响c1的

 

2.2、实现方式

那在JS里面对于这两种拷贝各自有什么实现方式呢?

浅拷贝:

  • Object.assign
  • 数组的concat方法
  • 数组的slice方法
  • 对象扩展运算符

1、Object.assign(target,source) : 将多个源对象中的属性复制到一个目标对象中

let person_obj = {
    person: {
        name:'eric',
        age: 18
    }
}
let obj = Object.assign({},person_obj)
console.log(obj == person_obj)
obj.person.name = 'jerry'
console.log(person_obj)

 


2、 展开运算符

let man = {
    name: 'tutu',
    age: 100,
    address: {
        country: 'china',
        city: 'beijing'
    }
}
let man_copy = {...man}
man.address.city = 'shanghai'
man.name = 'yiyi'
console.log(man_copy);


3、数组concat方法

let arr = [1,2,3,{name:'lala'}]
let arr2 = arr.concat()
arr2[3].name = 'bibi'
console.log(arr);


4、数组slice方法 

let array = [1,2,{address:'beijing'}]
let array1 = array.slice()
array[2].address = 'shanghai'
console.log(array1);


深拷贝:

  • JSON.stringify()
  •  循环递归

1、JSON.parse(JSON.stringify())

最简单的深拷贝方法,就是把一个对象序列化为JSON的字符串,并将对象里面的内容转为字符串,最后JSON.parse()将其再生成一个对象

let arr = [1,2,{name:'eric'}]
let newArr = JSON.parse(JSON.stringify(arr))
arr[0] = 2
arr[2].name = 'tom'
console.log(arr);
console.log(newArr);

 但需要注意:

  • 拷贝的对象的值如果有函数,undefined,symbol 这几种类型,经过 JSON.stringify 序列化后字符串中这个键值对会消失。
  • 拷贝 Date 类型会变成字符串
  • 无法拷贝不可枚举的属性
  • 无法拷贝对象原型链
  • 拷贝 RegExp 引用类型会变成空对象
  • 对象中含有 NaN、infinity 以及 -infinity,JSON 序列化后的结果变成 null
  • 无法拷贝对象的循环应用,即对象成环(obj[key]=obj)

3、手写代码

浅拷贝

function shallowCopy(target) {
    if(typeof target === 'object' && target !== null){  // 引用类型
        const copy = Array.isArray(target) ? [] : {}  // 创建数组或对象
        for(const prop in target){
            // 判断当前对象是否有自身的属性 不包括继承
            if(target.hasOwnProperty(prop)){
                copy[prop] = target[prop]
            }
        }
        return copy
    }
    // 基础类型,直接返回
    return target
}

 

深拷贝

简单版深拷贝

function deepCopy(obj){
    let copyObj = {}
    for(const key in obj){
        //  null 的 typeof 也是 object
        if(typeof obj[key] === 'object' && obj[key] !== null) {
            copyObj[key] = deepCopy(obj[key])
        }else{
            copyObj[key] = obj[key]
        }
    }
    return copyObj
}

const obj = {
    name: 'eric',
    age: 18,
    address: {
        country: 'china',
        city: 'beijing'
    }
}

const obj1 = deepCopy(obj)
obj1.name = 'tom'
obj1.address.city = 'shanghai'
console.log(obj,obj1); 

 上面,我们利用递归方式实现了一个简单的深拷贝。但依旧存在一些问题:

考虑数组

考虑循环引用

考虑Date或RegExp

function deepCopy(obj, hash = new WeakMap){
    // null或undefined
    if(obj == null) return obj
    // Data、RegExp、Error
    if(obj instanceof Date) return new Date(obj)
    if(obj instanceof RegExp) return new RegExp(obj)
    if(obj instanceof Error) return new Error(obj.message)
    // 基本类型
    if(typeof obj !== 'object') return obj
    // 对象进行深拷贝,判断是否存在该对象
    if(hash.has(obj)) return hash.get(obj)
    // 原型上的方法,可以获取对象上所有属性级特性
    const desc = Object.getOwnPropertyDescriptor(obj)
    // 获取原型上的方法和对象的描述信息,创建新的对象
    const copyObj = Object.create(Object.getPrototypeOf(obj), desc)
    hash.set(obj, copyObj)

    // 循环递归遍历内容
    for(const key of Reflect.ownKeys(obj)){  // Reflect.ownKeys 返回所有属性,包括不可枚举属性和Symbol类型
        let item = obj[key]
        if (typeof item === 'object' && item !== null && typeof item !== 'function') {
            copyObj[key] = deepCopy(item)
        } else {
            copyObj[key] = item
        }
    }
    return copyObj
}

const obj1 = {
    func: function() {console.log(1)},
    obj: { name: 'h' , data: { fn: function() { console.log('data') }, child: 'child' }},
    arr: [1,2,3],
    und: undefined,
    ref: /^123$/,
    date: new Date(),
    NaN: NaN,
    infinity: Infinity,
    sym: Symbol(1)
}
console.log(deepCopy(obj1));         


 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习HM微博项目第10天

步骤:发微博12-表情键盘06-点击表情 -> 发微博13-表情键盘07-插入表情和封装textView -> 发微博14-表情键盘08-长按表情 -> 发微博15-表情键盘09-最近表情 -> 发微博16-表情键盘10-最近表情完善 发微博12-表情键盘06-点击表情 APP的演示动画&#xff…

完全自学C(干货) —— 预处理详解

目录 一,预定义符号 二,#define #define定义的标识符 #define定义宏 # ## 带副作用的宏参数 宏和函数的对比 #undef 三,命令行定义 四,条件编译 五,文件包含 #include 六,其他预处理指令 一&…

搞的谁还不会爬福利美女跳舞视频一样,用我这个方法非常简单。

大家好啊!经常听别人说爬虫玩的好,*****!其实没有这么恐怖,爬虫你一般都是采集公开的信息,所以不会像网络传言那样,大家只要遵守协议,不会出问题的。 话说学编程语言的应该都是男孩子哈&#xf…

不解释

(1)业务线下 VS 线上大陆 VS 全球整合-国际规则合规企业 VS 产业极速联动-社会化资源调度(2)手段P:人工预测 VS 时序预测D:管理者人工指派任务 VS 运筹学最优求解C:人工检查监督审批工作流 VS …

第十五章 镜像架构和规划 - 示例镜像架构和网络配置

文章目录第十五章 镜像架构和规划 - 示例镜像架构和网络配置示例镜像架构和网络配置在单个数据中心、机房或校园内镜像配置简单故障转移对具有 DR 的故障转移配对和报告 Ayncs 同构连接第十五章 镜像架构和规划 - 示例镜像架构和网络配置 示例镜像架构和网络配置 本节描述并说…

Python用re模块使用正则表达式

Python正则表达式是一种强大的工具,用于在字符串中查找和匹配特定模式的文本。在Python中,可以使用re模块来使用正则表达式。正则表达式是一种模式匹配语言,可以在文本中寻找特定模式的字符串。正则表达式可以用于验证输入,搜索和…

MYSQL——美团面试题

MYSQL——美团面试题 2023/3/27 美团二面 题目描述 Create table If Not Exists courses (student varchar(255), class varchar(255));insert into courses (student, class) values (A, Math); insert into courses (student, class) values (B, English); insert into co…

《C++那些事》之开启你的BenchMark项目

《C那些事》之开启你的BenchMark测试0.导语本节目标:完成一个BenchMark小项目!在平时开发中,如何测试自己的接口性能呢?C里面如何快速搭建一个BenchMark测试框架呢?本节将Step By Step开启BenchMark入门的第一课。1.项目结构我们以…

灵动微基于 MM32SPIN040C 为主控的无感方波水泵应用方案

水泵是一种运输液体或增压液体的机器。将原动机的机械能或其他外部动能量传递给液体,提高液体动能,主要用于运输含水、油、酸碱液、乳化液、悬乳液和液体金属的液体。 介绍一款适用于水泵的32位单片机——MM32SPIN040C。 RAMSUN推荐一款应用于水泵的MC…

从零开始实现一个C++高性能服务器框架----协程调度模块

此项目是根据sylar框架实现,是从零开始重写sylar,也是对sylar丰富与完善 项目地址:https://gitee.com/lzhiqiang1999/server-framework 简介 项目介绍:实现了一个基于协程的服务器框架,支持多线程、多协程协同调度&am…

倒计时组件:可视化如何自定义目标时间 / 数字倒数

倒计时组件支持通过自定义目标时间或倒数数字,在报表和大屏中展示时间倒数和数字倒数。 下面以Sugar BI为例,为大家展示 倒计时展示模式 倒计时组件提供「时间倒数」和「数字倒数」两种展示模式,效果如下: 默认为「时间倒数」模…

将本地项目上传到远程仓库的步骤

文章目录将本地项目上传到远程仓库的步骤1.进入想上传的项目文件夹2.初始化本地仓库3.添加该项目下的所有文件4.将文件添加到本地仓库中5.添加远程仓库6.将文件更新到远程仓库上7.将本地文件推送回到指定的远程仓库中将本地项目上传到远程仓库的步骤 1.进入想上传的项目文件夹…

简单介绍TensorFlow中关于tf.app.flags命令行参数解析模块

这篇文章主要介绍了TensorFlow中关于tf.app.flags命令行参数解析模块,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教 tf.app.flags命令行参数解析模块 说道命令行参数解析,就不得不提到 python…

Spring的IOC和DI入门

1、相关概念 1.1、Spring来源 官网:Spring | Home Spring是一个分层的Java SE/EE应用一站式的轻量级开源框架。Spring核心是IOC和AOP。 Spring主要优点包括: 方便解耦,简化开发,通过Spring提供的IoC容器,我们可以将…

分布式事务问题

分布式事务问题 1、什么是分布式事务 一次课程发布操作需要向数据库、redis、elasticsearch、MinIO写四份数据,这里存在分布式事务问题。 什么是分布式事务? 首先理解什么是本地事务? 平常我们在程序中通过spring去控制事务是利用数据库…

【C++】一维数组练习案例 - 五只小猪称体重

目录 1、缘起 2、案例描述 3、代码 4、总结 1、缘起 最近在黑马程序员 UP 主那里学习 C 编程语言,学习到了【第44节】一维数组练习案例 - 五只小猪称体重 知识点。找出五只小猪中最重的小猪,这不就是基本算法中的 "求最大值算法" 嘛。 为…

ChatGPT相关核心算法

ChatGPT 的卓越表现得益于其背后多项核心算法的支持和配合。本文将分别介绍作为其实现基础的 Transformer 模型、激发出其所蕴含知识的Prompt/Instruction Tuning 算法、其涌现出的思维链能力、以及确保其与人类意图对齐的基于人类反馈的强化学习算法。 1.基于Transformer的预…

STM32F4_时钟系统精讲

目录 1. 什么是系统时钟 2. 时钟树 2.1 LSI 2.2 LSE 2.3 HSI 2.4 HSE 2.5 PLLCLK 2.6 SYSCLK 2.7 HCLK 2.8 PCLK1 2.9 PCLK2 2.10 RTC/AWU 3 SysTick定时器 3.1 为什么会有Systick定时器? 3.2 SysTick定时器的作用 3.3 SysTick定时器的寄存器 4.…

DAMA-CDGA/CDGP数据治理认证考试地点一般有哪些?

据目前数据统计,进行过DAMA-CDGA/CDGP数据治理认证考试的地区主要是一线城市及直辖市,主要有:北京、上海、广州、深圳、西安、杭州、成都、重庆、武汉、厦门、太原、甘肃、长沙等。(*当所在城市报名人数达到25人以上方可增加考场开…

C++基本语法

C 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。 对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类…