首页 > 编程语言 >一文弄懂Javascript中的深拷贝和浅拷贝

一文弄懂Javascript中的深拷贝和浅拷贝

时间:2024-03-23 23:34:01浏览次数:37  
标签:obj1 obj2 obj Javascript 弄懂 console const 拷贝

目录

一文弄懂Javascript 深拷贝与浅拷贝

1 Javascript数据存储规则

Javascript 中存在两大数据类型:

  • 基本类型

  • 引用类型

基本类型主要为以下6种,数据保存在在栈内存中:

  • Number
  • String
  • Boolean
  • Undefined
  • null
  • symbol

栈内存图解:

let a = 10;
let b = a;

b = 20;

// 给b赋值不影响a的值
console.log(a);  // 10;

栈内存

引用类型主要为以下三种,数据保存在堆内存中:

  • Object
  • Array
  • Function

引用数据类型的变量是一个指向堆内存中实际对象的引用,变量以及指向堆内存的指针存在在栈内存中;数据存在堆内存中。

堆内存图解:

var obj1 = {
    name: 'jone'
  };
// 将obj1的内存地址赋给obj2
var obj2 = obj1;

// 修改堆内存对象属性值
obj2.name = "sam";

console.log(obj1.name); // sam;对obj2的属性修改影响了obj1的属性;

堆内存

2 浅拷贝

浅拷贝,指的是创建新的栈内存数据,这个数据有着原始数据属性值的一份精确拷贝;

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址;

即浅拷贝是拷贝一层,对于基本类型来讲,第一层栈内存即可;

在Javascript 中,绝对浅拷贝的有:

  • 赋值运算符"="

上面的案例即为浅拷贝案例,只拷贝了第一层对象的栈内存村粗的变量以及引用地址;

var obj1 = {
    name: 'jone'
  };
// 将obj1的内存地址赋给obj2
var obj2 = obj1;

// 修改堆内存对象属性值
obj2.name = "sam";

console.log(obj1.name); // sam;对obj2的属性修改影响了obj1的属性;

3 部分深拷贝

部分深拷贝的意思就是不止拷贝一层,对数据拷贝两层及以上;但没有覆盖所有存储层级,称之为部分深拷贝;

以下运算可以实现一级引用类型的深拷贝,当有多级引用类型时,二级属性之后的就是浅拷贝,

  • Object.assign
  • Array.prototype.slice()
  • Array.prototype.concat()
  • 拓展运算...符实现的复制
3.1 Object.assign

Object.assign(target,source)方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target);

var obj = {
    age: 18,
    names: {
        name1: 'jone',
        name2: 'sam'
    }
}
var newObj = Object.assign({}, Obj);

// 修改对象的第一层属性
newObj.age =20;
// 修改对象的第二层属性
newObj.names.name1="change"

// 最后的结果是
console.log(obj);
/*
修改后的obj
{
    "age": 18,
    "names": {
        "name1": "change",
        "name2": "sam"
    }
*/

我们可以看到,修改新对象的第一层属性和第二层属性,源对象的第一层属性没有发生变化,第二层属性却发生了变化,原因是Object.assign只对原始栈内存和第一层属性重新赋予了存储空间,也就是说只拷贝了两层;所以才会出现如上效果,图解如下:

修改前:
在这里插入图片描述
修改后:
在这里插入图片描述
以下三个运算符的部分深拷贝原理与此相同;

3.2 slice()

slice()方法用于提取目标数组的一部分,返回一个新数组,原数组不变。它的第一个参数为起始位置(从0开始,会包括在返回的新数组之中),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员。

const Arr = ["One", "Two", "Three"]
const newArr = Arr.slice(0)
newArr[1] = "love";

console.log(Arr) // ["One", "Two", "Three"]
console.log(newArr) // ["One", "love", "Three"]
3.3 concat()

concat方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变。

const Arr = ["One", "Two", "Three"]
const newArr = Arr.concat()
newArr[1] = "love";

console.log(Arr) // ["One", "Two", "Three"]
console.log(newArr) // ["One", "love", "Three"]
3.4 拓展运算符
const Arr = ["One", "Two", "Three"]
const newArr = [...Arr]
newArr[1] = "love";

console.log(Arr) // ["One", "Two", "Three"]
console.log(newArr) // ["One", "love", "Three"]

4 完全深拷贝

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:

  • _.cloneDeep()
  • 结构化拷贝
  • jQuery.extend()
  • JSON.stringify()
  • 手写循环递归

深拷贝和浅拷贝区别如图所示:

区别

下面我们看一下这五种实现深拷贝的代码;

4.1_.cloneDeep()

借助lodash包的_.cloneDeep()方法实现深拷贝

const _ = require('lodash');
const obj1 = {
    a: 1,
    b: { 
        f: { 
            g: 1 
        } 
    },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);

console.log(obj1.b.f === obj2.b.f);// false,两者不相等说明指向的地址不同
4.2 结构化拷贝

结构化克隆算法是由HTML5规范定义的用于复制复杂|avaScript对象的算法。通过来自Workers的postMessage()或使用 IndexedDB 存储对象时在内部使用。它通过递归输入对象来构建克隆,同时保持先前访问过的引用的映射,以避免无限遍历循环。

function structuralClone(obj){
    return new Promise(resolve =>{
        const {port1,port2}= new MessageChannel();
        port2.onmessage=ev =>resolve(ev.data);
        port1.postMessage(obj);
    });
}

const obj1 = {
    a: 1,
    b: { 
        f: { 
            g: 1 
        } 
    },
    c: [1, 2, 3]
};
const obj2 = await structuralClone(obj1);

console.log(obj1.b.f === obj2.b.f);// false,两者不相等说明指向的地址不同

此方法一般情况下能够解决大部分问题,且性能较好。但不支持Function数据类型。

4.3 json.stringify()
const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

这种方式存在弊端,会忽略undefined、symbol 和函数

4.4 循环递归

通过遍历对象属性进行循环浅拷贝。只是在遇到一个 object 属性时,需要再次调用拷贝函数。

function deepClone(obj){
  if(typeof obj !== "object") return;    
  let newObj = obj instanceof Array ? [] : {};
  for(let key in obj){
     if(obj.hasOwnProperty(key)){
        newObj[key] = typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key];
    }      
  }  
  return newObj;  
}
let obj = {a: 11, b: function(){}, c: {d: 22}};
deepClone(obj);  // {a: 11, b: f(), c: {d: 22}};
4.5 jQuery.extend()

与lodash库相同,jQuery也提供了深拷贝方法,使用方法如下:

const $ = require('jquery');
const obj1 = {
    a: 1,
    b: { 
        f: { 
            g: 1 
        } 
    },
    c: [1, 2, 3]
};
const obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false,两者不相等说明指向的地址不同

5 总结

深拷贝和浅拷贝的区别就在于拷贝的层级,在日常使用中,我们可以按需去是使用拷贝方法:

  1. 基本类型使用普通赋值运算符;
  2. 引用类型:
  • 一维数据结构的深拷贝方法建议使用:Object.assign()

  • 二维数据结构及以上的深拷贝方法建议使用:JSON.parse(JSON.stringify())

  • 特别复杂的数据结构的深拷贝方法建议使用:三方API;

标签:obj1,obj2,obj,Javascript,弄懂,console,const,拷贝
From: https://blog.csdn.net/m0_46309087/article/details/136977628

相关文章

  • 实验:基于Red Hat Enterprise Linux系统在终端使用vim进行拷贝、删除、查找、替换、保
    目录一.实验目的二.实验内容三.实验设计描述及实验结果        一.vim文本编译器模式切换:    命令模式:        输入模式:        末行模式:        二.复制、删除:        三.查找字符串:        四.替换:......
  • JavaScript原型、原型对象、原型链系列详解(一)
    (一)、JavaScript原型原型JavaScript是一门面向对象的编程语言,其中原型(prototype)是一个重要的概念,它提供了一种创建对象的方式,使对象可以共享属性和方法。在JavaScript中,每个对象都有一个原型,可以从原型中继承属性和方法。原型的定义JavaScript的原型是一个对象,它......
  • SH文件从Window拷贝到Linux运行失败
    1.问题现象bash:./startup.sh:/bin/bash^M:解释器错误:没有那个文件或目录这个错误通常发生在尝试在Unix-like系统中执行脚本时,脚本文件的行尾结束符是Windows风格的CRLF(回车+换行,即\r\n),而不是Unix风格的LF(换行,即\n)。/bin/bash^M说明了这个问题,^M是字符\r的控制台输出表......
  • Javascript学习笔记
    Javascript基础   js是什么?         定义       是一种运行在客户端(浏览器)的编程语言,实现人机交互效果      html和css只是标记语言,并没有涉及编程的部分    作用      网页特效(监听用户的一些行为让网页做......
  • JavaScript高级(十)----JavaScript中的类【重述原型链】!
     类在JavaScript其实本来没有类的概念,哪怕是ES5以后的class,严格意义上来说也只是构造函数的语法糖,之所以喜欢称之为类,因为JavaScript也可以面向对象开发。类的声明classPerson{}functionPerson1(){}//上面两种写法本质上是一样的console.log(typeofPerson)cons......
  • JavaScript高级(九)---JavaScript的六种继承方式
    1、原型继承实现:1234567functionSuper(){this.a=1}Super.prototype.say=function(){console.log(‘hhh')}functionSub(){}Sub.prototype=newSuper()consttest=newSub()console.log(test.say())//hhh优点:通过原型继承多个引用类型的属性和......
  • 【华为OD】2024年C卷真题集:最新的真题集题库 C/C++/Java/python/JavaScript
    2024年C卷真题题集题库,有2种分数的题目列表,分别是100分的列表、200分的列表需要订阅请看链接:C卷100分真题集质量分:94价格:39.9元C卷200分真题集质量分:94价格:99.9元从2023年11月开始,华为OD题目切换到C卷,基本上大概率会使用半年左右,要在2024年5月之前把这些题目都好好练习一下......
  • 【React】使用 JSX 为 JavaScript 添加标签
    使用JSX为JavaScript添加标签实际上是将JSX语法与JavaScript代码结合使用,以描述用户界面。JSX允许你在JavaScript中编写类似HTML的结构,并最终由React库将其转换为真正的DOM元素。以下是将标签引入JavaScript以及将HTML转化为JSX的步骤和JSX的一些基本......
  • JavaScript之Promise补充与Dom操作
    Promise过程分析//按照顺序依次引入a-d.js,最后打印加载完毕load('a.js').then(()=>{returnload('b.js')//load方法返回Pomise对象//但是没有把这个对象返回//所以这个函数没有返回值//then方法会提供一个空对象作为返......
  • 关于javaScript的计算精度的解决办法
    项目中我们常常需要做一些计算,由于浮点数的二进制表示可能不精确,经常会遇到计算精度问题,例letresultNum=0.1+0.2;console.log(resultNum);//0.30000000000000004这个时候,如果我们不单独处理,那么页面上展示的时候就出现布局错乱等问题,比如我们可以保留两位小数采用Number(r......