深浅拷贝,温故知新

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 浅拷贝

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

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

posted @ 2023-05-11 09:53  ESnail  阅读(359)  评论(0编辑  收藏  举报