js中区分深拷贝与浅拷贝的实战过程

目录
  • 一、自我理解
  • 二、数据存储形式
    • (1)基本数据类型存储于栈中
    • (2)引用数据类型存储与堆中
  • 三、怎样实现深拷贝?
    • (1)借助JSON对象的parse和stringify
    • (2)手写递归
    • (3)JQuery中extend方法
  • 总结/注意
  • 总结

一、自我理解

简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层。

在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改。

在深拷贝中,原对象与新对象不共享相同的属性,而在浅拷贝中,它们具有相同的属性。

举个栗子:存在A和B两个数据,假设B复制了A,当修改A时,如果B跟着A变化了,则是浅拷贝;如果B不受A的改变而改变,则是深拷贝

let A = [0,1,2]
let B = A
console.log(A === B);
A[0] = 3
console.log(A,B);

结果如下:

这就是一个简单的浅拷贝例子,虽然B复制了A,但是修改了A,B却跟着A变化了?

二、数据存储形式

通过上述栗子,我们就需要了解数据类型的存储方式了,如果还没有了解数据类型的小伙伴欢迎阅读JS中8种数据类型

  • 基本数据类型:number,string,boolean,null,undefined,symbol(es6),还有谷歌67版本中的BigInt(任意精度整数)
  • 引用数据类型:Object【Object是个大类,function函数、array数组、date日期...等都归属于Object】

(1)基本数据类型存储于栈中

变量名和值都存储与栈中

每当声明一个变量,就会开辟一个新的内存空间来存储值,例如:let a = 1

// 声明一个变量
let a = 1;

当b复制了a后,因为b也是一个变量,所以栈内存会再开辟一个新的内存空间:

// 声明一个变量
let a = 1;
let b = a; //b变量复制a变量
console.log(a,b); //1 1

这就一目了然了,a和b两个变量归属于不同的内存空间,所以当你修改其中一个值,另外一个值不会受其影响!

// 声明一个变量
let a = 1;
let b = a; //b变量复制a变量
console.log(a,b); //1 1
// 修改a的值不影响b
a = 123;
console.log(a,b); //123 1

(2)引用数据类型存储与堆中

变量名存储在栈中,值存在于堆中,这时栈内存中会提供一个指针用于指向堆内存中的值

当我们声明一个引用数据类型时,会提供一个指针指向堆内存中的值:

// 声明一个引用类型的值
let a = [0,1,2,3,4]

当b复制了a后,只是复制了当前栈内存中提供的指针,指向同一个堆内存中的值,并不是复制了堆内存中的值:

let a = [0,1,2,3,4]
let b = a //复制的是一个指针,而不是堆中的值

所以当我们进行a[0]=1时,导致指针所指向的堆内存中的值发生了改变,而a和b是同一个指针,指向同一个堆内存地址,所以b的值也会受影响,这就是浅拷贝!

// 声明一个引用类型的值
let a = [0,1,2,3,4];
let b = a; //复制的是一个指针,而不是堆中的值
console.log(a === b);
a[0] = 1; //改变a数组值,b数组跟着改变(浅拷贝)
console.log(a,b);

所以,我们需要在堆内存中新开辟一个内存专门接收存储b的值,道理与基本数据类型一样,这样就可以实现深拷贝(修改一个值不会影响另一个值的变化):

三、怎样实现深拷贝?

(1)借助JSON对象的parse和stringify

parse()方法用于将一个字符串解析为json对象

stringify()方法用于将一个对象解析为字符串

// 浅拷贝
let arr = [1,"hello",{name:"张三",age:21}]
let copyArr = arr
console.log(arr === copyArr); //true
arr[1] = "您好"
console.log("浅拷贝",arr, copyArr);

// JSON对象方法实现深拷贝
let arr = [1,"hello",{name:"张三",age:21}]
let newArr = JSON.parse(JSON.stringify(arr)) //将arr数组转换为字符串,再作为参数转化为对象
arr[1] = "您好"
arr[2].name = "法外狂徒"
console.log("深拷贝",arr, newArr);

原理是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,这样在堆中开辟了新的内存,实现深拷贝

这种写法非常简单,而且可以应对大部分的应用场景,但是它还是有很大缺陷的,比如拷贝其他引用类型、拷贝函数、循环引用等情况

(2)手写递归

递归方法实现深度克隆原理:遍历对象、数组...直到里边都是基本数据类型,然后再去复制,就是深度拷贝

  • 如果是原始类型,无需继续拷贝,直接返回
  • 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上
// 手写递归实现深拷贝
function clone(target) {
    if (typeof target === 'object') {
        // 创建一个新对象
        let cloneTarget = {};
        // 遍历需要克隆的对象
        for (const key in target) {
            // 将需要克隆对象的属性执行深拷贝后依次添加到新对象上
            cloneTarget[key] = clone(target[key]);
        }
        // 返回克隆对象
        return cloneTarget;
    } else {
        return target;
    }
};
// 模拟拷贝对象
const person = {
    name: "李小白",
    age: 18,
    job: {
        name:"web前端",
        salary:"15k",
        address:"成都"
    }
};
// 将需要克隆的对象传递给clone函数作为参数,接收一个克隆对象作为返回结果
let newTarget = clone(person)
//修改原对象属性
person["name"] = "李大白"
person["job"]["salary"] = "25k"
console.log(person,newTarget); //实现深拷贝

利用递归可以实现深度拷贝,但是有局限性,例如数组还没考虑进去!

想要兼容数组其实很简单,直接判断一下参数是不是数组,然后利用三木运算决定是创建一个新对象还是一个新数组:

function clone(target) {
    if (typeof target === 'object') {
        let isArr = Array.isArray(target)
        // 判断传入的参数是数组吗 ? 是则赋值[] : 否则赋值{}
        let cloneTarget = isArr ? [] : {};
        // 遍历需要克隆的对象
        for (const key in target) {
            // 将需要克隆对象的属性执行深拷贝后依次添加到新对象上
            cloneTarget[key] = clone(target[key]);
        }
        // 返回克隆对象
        return cloneTarget;
    } else {
        return target;
    }
};

(3)JQuery中extend方法

在jQuery中提供一个$extend()来实现深拷贝

语法:

$.extend( [deep ], target, object1 [, objectN ] )
  • deep 表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
  • target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
  • object1  objectN可选。 Object类型 第一个以及第N个被合并的对象。
let arr = ["李小白",18,["前端","15k"],21]
// true:开启深拷贝,目标对象:[],原对象:arr
let cloneArr = $.extend(true, [], arr)
arr[0] = "李大白";
arr[2][0] = "java";
console.log(arr, cloneArr);

$extend()第一个参数为true时可以实现深拷贝!但是第一个参数为false,则只会拷贝第一层属性,不能实现深拷贝:

let arr = ["李小白",18,["前端","15k"],21]
// false:不开启深拷贝,目标对象:[],原对象:arr
let cloneArr = $.extend(false, [], arr)
arr[0] = "李大白";
arr[2][0] = "java";
console.log(arr, cloneArr);

总结/注意

实现深拷贝方式有多种,以上三种较为常用,另外还有一个:函数库lodash中_.cloneDeep方法(本人未使用过)也可以实现深拷贝。

最后需要补充一下,网上有很多人说slice和concat方法可以实现,但我要说的是slice和concat方法不能实现深拷贝的!!!

下面举例说明:

// slice()
let arr = [0,1,["hello","world"]];
let cloneArr = arr.slice();
arr[0] = "零";
arr[2][0] = "hello2022"
console.log(arr,cloneArr);
// concat()
let arr = [0,1,["hello","world"]];
let cloneArr = arr.concat();
arr[0] = "零";
arr[2][0] = "hello2022"
console.log(arr,cloneArr);

这两个方法得到的结果都是一样的:

这一点大家一定要注意,深拷贝必须要拷贝所有层级的属性,而这两个方法拷贝不彻底,也就是只能拷贝第一层,希望大家不要踩坑!

总结

到此这篇关于js中区分深拷贝与浅拷贝的文章就介绍到这了,更多相关js区分深拷贝与浅拷贝内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript中深拷贝与浅拷贝详解

    目录 1 浅拷贝概念 2 深拷贝概念 3 浅拷贝的实现方式 3.1 Object.assign() 3.2 Array.prototype.concat() 3.3 Array.prototype.slice() 3.4 直接赋值 4 深拷贝的实现方式 4.1 JSON.parse(JSON.stringify()) 4.2 函数库lodash 总结 1 浅拷贝概念 深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的. 浅拷贝是创建一个新对象,该对象有着原始对象属性值的一份精确拷

  • js中的赋值 浅拷贝和深拷贝详细

    目录 1.js内存 2.赋值 3.浅拷贝 4.深拷贝 前言: 在学习下面文章前我们简单了解一下的内存的知识,以下先简要提一下 1.js内存 js内存,或者说大部分语言的内存都分为栈和堆.基本数据类型的变量值分配在栈上,引用数据类型的变量值分配在堆上,栈中只是存储具体堆中对象的地址. 2.赋值 对于基本数据类型,赋值操作是拷贝,即新旧变量不会相互影响. var a = 1; var b = a; b = 2; console.log(b); // 2 对于引用数据类型,赋值操作只是在栈中新增一个指

  • 浅谈JavaScript浅拷贝和深拷贝

    目录 一.直接赋值 二.浅拷贝 三.深拷贝 1. JSON对象的方式 2. 递归复制 网上关于这个话题,讨论有很多了,根据各路情况我自己整理了一下,最后还是能接近完美的实现深拷贝,欢迎大家讨论. javascript中的对象是引用类型,在复制对象的时候就要考虑是用浅拷贝还是用深拷贝. 一.直接赋值 对象是引用类型,如果直接赋值给另外一个对象,那么只是赋值一个引用,实际上两个变量指向的同一个数据对象,如果其中一个对象的属性变更,那么另外一个也会变更. 示例1,简单示例: let human1 =

  • 详解JS变量存储深拷贝和浅拷贝

    变量类型与存储空间 栈内存和堆内存 基本数据类型 string.number.null.undefined.boolean.symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值 基本数据类型不存在拷贝,好比如说你无法修改数值1的值 引用类型 Object Function RegExp Math Date 值为对象,存放在堆内存中 在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址. 当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据

  • 详解JS深拷贝与浅拷贝

    一.预备知识 1.1.JS数据类型 基本数据类型:Boolean.String.Number.null.undefined 引用数据类型:Object.Array.Function.RegExp.Date等 1.2.数据类型的复制 基本数据类型的复制,是按值传递的 var a = 1; var b = a; b = 2; console.log(a); // 1 console.lob(b); // 2 引用数据类型的复制,是按引用传值 var obj1 = { a: 1; b: 2; }; v

  • js中区分深拷贝与浅拷贝的实战过程

    目录 一.自我理解 二.数据存储形式 (1)基本数据类型存储于栈中 (2)引用数据类型存储与堆中 三.怎样实现深拷贝? (1)借助JSON对象的parse和stringify (2)手写递归 (3)JQuery中extend方法 总结/注意 总结 一.自我理解 简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层. 在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改. 在深拷贝中,原对象与新对象不共享相同的属性,而在浅拷贝中,它们具有相同的属性. 举个栗子:存

  • JS对象复制(深拷贝和浅拷贝)

    一.浅拷贝 1.Object.assign(target,source,source...) a.可支持多个对象复制 b.如果source和target属性相同 source会复制target的属性 c.target只能为Object对象 var obj = {a:1,b:2} undefined Object.assign({c:3},obj) {c: 3, a: 1, b: 2} obj {a: 1, b: 2} 兼容性写法if(Object.assign){//兼容}else{//不兼容}

  • js中对象深拷贝方法总结

    快速克隆(存在数据丢失问题) – JSON.parse/stringify 如果不在对象中使用Date.functions.undefined.Infinity.RegExps.Maps.Sets.blob.FileLists.ImageDatas.或其他复杂类型,则深入克隆对象库可以使用非常简单的一行代码. 简单的来说有以下问题: 会忽略 undefined 会忽略 symbol 不能序列化函数 不能解决循环引用的对象 JSON.parse(JSON.stringify(object)) co

  • 详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    1.序列化实现 public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T object){ T cloneObj = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new Objec

  • 一文带你搞懂Numpy中的深拷贝和浅拷贝

    目录 1. 引言 2. 浅拷贝 2.1 问题引入 2.2 问题剖析 3. 深拷贝 3.1 举个栗子 3.2 探究原因 4. 技巧总结 4.1 判断是否指向同一内存 4.2 其他数据类型 5. 总结 1. 引言 深拷贝和浅拷贝是Python中重要的概念,本文重点介绍在NumPy中深拷贝和浅拷贝相关操作的定义和背后的原理. 闲话少说,我们直接开始吧! 2. 浅拷贝 2.1 问题引入 我们来举个栗子,如下所示我们有两个数组a和b,样例代码如下: import numpy as np a = np.ar

  • Golang中的深拷贝与浅拷贝使用

    目录 一.概念 1.深拷贝(Deep Copy) 2.浅拷贝(Shallow Copy) 二.本质区别 三.示例 浅拷贝 深拷贝 参考: 一.概念 1.深拷贝(Deep Copy) 拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值.既然内存地址不同,释放内存地址时,可分别释放. 值类型的数据,默认全部都是深复制,Array.Int.String.Struct.Float,Bool. 2.浅拷贝(Shal

  • 通过源码分析iOS中的深拷贝与浅拷贝

    前言 关于iOS中对象的深拷贝和浅拷贝的文章有很多,但是大部分都是基于打印内存地址来推导结果,这篇文章是从源码的角度来分析深拷贝和浅拷贝. 深拷贝和浅拷贝的概念 拷贝的方式有两种:深拷贝和浅拷贝. 浅拷贝又叫指针拷贝,比如说有一个指针,这个指针指向一个字符串,也就是说这个指针变量的值是这个字符串的地址,那么此时对这个字符串进行指针拷贝的意思就是又创建了一个指针变量,这个指针变量的值是这个字符串的地址,也就是这个字符串的引用计数+1. 深拷贝又叫内容拷贝,比如有一个指针,这个指针指向一个字符串,也

  • three.js中正交与透视投影相机的实战应用指南

    目录 前言 1 正交投影相机 2 投射投影相机 3 实例 总结 前言 一个场景之所以会呈现在我们眼前是因为我们具有眼睛,眼睛提供了视觉.换句话说,如果three.js场景中没有这双眼睛,就像电影没有摄像机一样,场景就无法呈现在我们面前?这双眼睛就是相机,可见相机是Three.js场景中不可或缺的一个组件.Three.js库提供了两种不同的相机:正交投影相机和透视投影相机,接下来分别讲解这两种相机以及结合实例的应用. 1 正交投影相机 我们先来看一张示意图: 由图可知正交透视相机总共有6个面,其具

  • Python中的深拷贝和浅拷贝详解

    要说清楚Python中的深浅拷贝,需要搞清楚下面一系列概念: 变量-引用-对象(可变对象,不可变对象)-切片-拷贝(浅拷贝,深拷贝) [变量-对象-引用] 在Python中一切都是对象,比如说:3, 3.14, 'Hello', [1,2,3,4],{'a':1}...... 甚至连type其本身都是对象,type对象 Python中变量与C/C++/Java中不同,它是指对象的引用,Python是动态类型,程序运行时候,会根据对象的类型 来确认变量到底是什么类型. 单独赋值: 比如说: 复制代

随机推荐