深浅拷贝,温故知新

1、深拷贝

1.1、概念

对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本。

因此,当你更改源或副本时,可以确保不会导致其他对象也发生更改;也就是说,你不会无意中对源或副本造成意料之外的更改。

在深拷贝中,源和副本是完全独立的。深拷贝与其源对象不共享引用,所以对深拷贝所做的任何更改都不会影响源对象。

1.2、实现方式:

1.2.1、使用 JSON.stringify() 将该对象转换为 JSON 字符串,然后使用 JSON.parse() 将该字符串转换回(全新的)JavaScript 对象。

前提:JavaScript 对象可以被序列化

序列化异常报错

  • 存在循环引用时,会抛出异常 TypeError ("cyclic object value")(循环对象值)
  • 存在 BigInt 类型的值时,如:{ num: BigInt(1111111111) } 会抛出 TypeError ("BigInt value can't be serialized in JSON")(BigInt 值不能 JSON 序列化).

序列化需要注意的隐式转换

  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如 JSON.stringify(function(){}) or JSON.stringify(undefined).
  • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
  • Date 日期,会调用 toJSON() 将其转换为 string 字符串(同 Date.toISOString())
  • NaNInfinity 格式的数值及 null 都会被当做 null
  • Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
  • HTML 元素对象, 会得到 {}

示例

  // 隐式转换

  JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
  // '[1,"false",false]'

  JSON.stringify({x: undefined, y: Object, z: Symbol("")});
  // '{}'

  JSON.stringify([undefined, Object, Symbol("")]);
  // '[null,null,null]'

  JSON.stringify({[Symbol("foo")]: "foo"});
  // '{}'

  JSON.stringify({[Symbol.for("foo")]: "foo"}, [Symbol.for("foo")]);
  // '{}'

  JSON.stringify(
      {[Symbol.for("foo")]: "foo"},
      function (k, v) {
          if (typeof k === "symbol"){
              return "a symbol";
          }
      }
  );

  // undefined

  // 不可枚举的属性默认会被忽略:
  JSON.stringify(
      Object.create(
          null,
          {
              x: { value: 'x', enumerable: false },
              y: { value: 'y', enumerable: true }
          }
      )
  );

  // "{"y":"y"}"

  JSON.stringify(document.body) // '{}'

  JSON.stringify(new Set([1, 2, 3])) // '{}'

  JSON.stringify(new Map([['num', 2]])) // '{}'
1.2.2、使用 window.structuredClone(),使用结构化克隆算法将给定的值进行深拷贝。

前提:可序列化的对象,且在浏览器环境和任何其他实现了 window 这样全局对象的 JavaScript 运行时的环境(structuredClone() 不是 JavaScript 语言本身的特性)

优点

  • 支持把原始值中的可转移对象转移到新对象,而不是把属性引用拷贝过去。
    • 可转移对象与原始对象分离并附加到新对象; 它们不可以在原始对象中访问被访问到。
  • 支持循环引用

补充:结构化克隆算法

  • 结构化克隆算法是用于复制复杂 js 对象的算法。
  • 它通过递归输入对象来构建克隆,同时保持先前访问过的引用的映射,以避免无限遍历循环。
  • Worker 的 postMessage()IndexedDB 存储对象时内部使用该算法。所以,也可间接的通过这2个方法实现深拷贝。

补充:可转移对象

  • 可转移的对象(Transferable object)是拥有属于自己的资源的对象,这些资源可以从一个上下文转移到另一个,确保资源一次仅在一个上下文可用。传输后,原始对象不再可用;它不再指向转移后的资源,并且任何读取或者写入该对象的尝试都将抛出异常。
  • 可转移对象通常用于共享资源,该资源一次仅能安全地暴露在一个 js 线程中。
  • 如:ArrayBuffer 是一个拥有内存块的可转移对象。当此类缓冲区(buffer)在线程之间传输时,相关联的内存资源将从原始的缓冲区分离出来,并且附加到新线程创建的缓冲区对象中。原始线程中的缓冲区对象不再可用,因为它不再拥有属于自己的内存资源了。

异常报错

  • Function 对象是不能被结构化克隆算法复制的,抛出 DATA_CLONE_ERR 异常,如:structuredClone(function fn() {})
  • Symbol 不能被结构化克隆算法复制的,抛出 DATA_CLONE_ERR 异常,如:structuredClone({s: Symbol(1)})
  • HTML 元素对象,抛出 DATA_CLONE_ERR 异常,如:structuredClone(document.body)

隐式转换

  • RegExp 对象的 lastIndex 字段不会被保留
  • 属性描述符,setters 以及 getters(以及其他类似元数据的功能)同样不会被复制。例如,如果一个对象用属性描述符标记为 read-only,它将会被复制为 read-write,因为这是默认的情况下
  • 原形链上的属性也不会被追踪以及复制

支持的js类型

支持的 Error类型

示例

// 循环引用
const original = { name: "MDN" };
original.itself = original;

// clone
const clone = structuredClone(original);

// 验证
console.log(clone !== original) // true
console.log(clone.name === "MDN") // true
console.log(clone.itself === clone) // true


const get = { get foo() { return 'bar' } }
console.log(get.foo) // 'bar'


class MyClass { 
  foo = 'bar' 
  myMethod() { /* ... */ }
}
const myClass = new MyClass()

const cloned = structuredClone(myClass)
// { foo: 'bar' }

cloned instanceof myClass // false
1.2.3、 使用js库

比如 lodash 中的 cloneDeep() 方法

该方法会递归拷贝 value。

clone 方法参考自 结构化克隆算法 以及支持 arraysarray buffersbooleansdate objectsmapsnumbersObject 对象, regexes, sets, strings, symbols, 以及 typed arraysarguments 对象的可枚举属性会拷贝为普通对象。 一些不可拷贝的对象,例如 error objectsfunctions, DOM nodes, 以及 WeakMaps 会返回空对象。

参考:MDN

2、浅拷贝

2.1、概念

对象的浅拷贝是其属性与拷贝源对象的属性共享相同引用(指向相同的底层值)的副本。

因此,当你更改源或副本时,也可能导致其他对象也发生更改——也就是说,你可能会无意中对源或副本造成意料之外的更改。

在浅拷贝中,对源或副本的更改可能也会导致其他对象的更改(因为两个对象共享相同的引用)。

2.2、实现方式

在 js 中,所有标准的内置对象进行操作:...展开语法、Array.prototype.concat()Array.prototype.slice()Array.from()Object.assign()Object.create(),创建的都是浅拷贝而不是深拷贝。

参考:MDN 浅拷贝

最后,感谢您阅读这篇博客!希望本文能够为您提供有价值的信息和启示。

如果您对本文的内容有任何疑问或建议,请随时在评论区留言,我会尽快回复您。

原文链接:https://www.cnblogs.com/EnSnail/p/17390161.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:深浅拷贝,温故知新 - Python技术站

(0)
上一篇 2023年5月11日
下一篇 2023年5月11日

相关文章

  • JS数据类型判断的几种常用方法

    一、题目背景在JavaScript编程中,我们经常需要对数据的类型进行判断,以便进行不同的操作。本文详细讲解了JS数据类型判断的几种常用方法。 二、常用方法1. typeof 运算符这是最常用的判断数据类型的方式。它可以返回一个字符串,表示操作数的类型。它可以判断基本数据类型、“function”和“undefined”类型,但不能判断具体的引用类型。使用方…

    JavaScript 2023年5月27日
    00
  • JavaScript URL参数读取改进版

    下面我来详细讲解一下“JavaScript URL参数读取改进版”的完整攻略。 一、背景介绍 在前端开发中,我们经常需要从URL中获取参数,以便进行后续操作。而通过JavaScript获取URL参数是一种常见且重要的操作。 然而,传统的JavaScript URL参数读取方法存在一些问题,比如需要手动解析URL,代码冗长,逻辑混乱等。这些问题导致了使用不便、…

    JavaScript 2023年5月19日
    00
  • JavaScript实现的encode64加密算法实例分析

    JavaScript实现的encode64加密算法实例分析 简介 encode64是一种基于64个可打印字符来表示二进制数据的编码方式。相比于普通的ASCII码编码而言,它可以更加节约空间。这种编码方式常用于在网络传输中对一些隐私数据进行加密保护。 实现原理 encode64算法的实现原理如下: 将原始数据(二进制)每6位一组,转换成相应的十进制数。 根据以…

    JavaScript 2023年6月1日
    00
  • 在javascript中随机数 math random如何生成指定范围数值的随机数

    首先需要了解 Math.random() 方法可以生成一个在0(包含0)到1(不包括1)之间的一个伪随机数。要生成指定范围内的随机数,需要通过一些计算和转换来实现。以下是一些可能的做法: 做法一:生成任意两数之间的随机数 可以先生成一个在0到1之间的随机小数,然后将其乘以两个数的范围,再加上较小的数,从而实现生成任意两数之间的随机数。 function ra…

    JavaScript 2023年6月10日
    00
  • js中console在一行内打印字符串和对象的方法

    在JavaScript中,我们常常需要在控制台(console)输出调试信息,其中输出的内容可能是一些字符串、数字和对象等。有时候我们想要将多个输出信息连成一行,这时候就可以使用一些方法来实现。 1. 使用字符串拼接符号 ‘+’ 在JavaScript中,我们可以使用字符串拼接符号 ‘+’ 来将多个字符串拼接在一起,例如: console.log(‘hell…

    JavaScript 2023年5月28日
    00
  • JS正则获取HTML元素的方法

    当我们在开发Web应用时,经常需要在DOM中根据正则表达式来查找和获取特定的HTML元素。如何使用JavaScript正则表达式来处理DOM的HTML元素呢?下面是一些方法: 使用JavaScript内置函数来获取HTML元素 JavaScript通过document对象来表示整个HTML文档。document对象上使用的内置函数可以轻松地获取DOM元素。通…

    JavaScript 2023年6月10日
    00
  • 详解JavaScript中常用操作符的使用

    详解JavaScript中常用操作符的使用 前言 JavaScript中操作符是用来执行各种计算操作的符号,不同的操作符有不同的用途和优先级。在编写JavaScript程序时,我们需要了解各种操作符的使用方法和规则。本文将详细介绍JavaScript中常用操作符的使用。 算术操作符 算术操作符是用于执行基本的算术计算,如加减乘除等操作。下面是常用的算术操作符…

    JavaScript 2023年5月27日
    00
  • JavaScript中数组去除重复的三种方法

    以下是“JavaScript中数组去除重复的三种方法”的完整攻略。 方法一:使用双重循环 算法思路 使用一个外层循环遍历数组元素,然后在外层循环内部再嵌套一个内层循环遍历前面的元素,依次与当前元素比较,如果有相同的就将其删除。 代码示例 function unique1(arr) { for (var i = 0; i < arr.length; i+…

    JavaScript 2023年5月27日
    00
合作推广
合作推广
分享本页
返回顶部