>  기사  >  웹 프론트엔드  >  JavaScript 딥 카피 성능에 대한 깊은 이해

JavaScript 딥 카피 성능에 대한 깊은 이해

小云云
小云云원래의
2018-02-03 14:28:232058검색

이 기사에서는 주로 JavaScript 딥 카피 성능 분석을 공유합니다. JavaScript에서 객체를 복사하는 방법은 무엇입니까? 이것은 매우 간단한 질문이지만 대답은 간단하지 않습니다.

무슨 뜻인지 모르신다면 다음 예를 살펴보세요.

function mutate(obj) {
  obj.a = true;
}

const obj = {a: false};
mutate(obj)
console.log(obj.a); // 输出 true

mutate 함수는 매개변수를 변경합니다. 값별 전달 시나리오에서 함수의 형식 매개변수는 실제 매개변수의 복사본(복사본)일 뿐이며 실제 매개변수는 함수 호출이 완료된 후에 변경되지 않습니다. 그러나 JavaScript의 참조에 의한 전달 시나리오에서는 함수의 형식 매개변수와 실제 매개변수가 동일한 객체를 가리킵니다. 형식 매개변수가 매개변수 내부에서 변경되면 함수 외부의 실제 매개변수도 변경됩니다. 변경되었습니다. mutate 改变了它的参数。在值传递的场景中,函数的形参只是实参的一个副本——a copy——当函数调用完成后,并不改变实参。但是在 JavaScript 这种引用传递的场景中,函数的形参和实参指向同一个对象,当参数内部改变形参的时候,函数外面的实参也被改变了。

因此在某些情况下,你需要保留原始对象,这时你需要把原始对象的一个拷贝传入到函数中,以防止函数改变原始对象。

浅拷贝:Object.assign()

一个简单的获取对象拷贝的方式是使用 Object.assign(target, sources...)。它接受任意数量的源对象,枚举它们的所有属性并分配给target。如果我们使用一个新的空对象target,那么我们就可以实现对象的复制。

const obj = /* ... */;
const copy = Object.assign({}, obj);

然而这只是一个副本。如果我们的对象包含其它对象作为自己的属性,它们将保持共享引用,这不是我们想要的:

function mutateDeepObject(obj) {
  obj.a.thing = true;
}

const obj = {a: {thing: false}};
const copy = Object.assign({}, obj);
mutateDeepObject(copy)
console.log(obj.a.thing); // prints true
Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 gettersetter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()Object.defineProperty()

所以现在怎么办?有几种方法可以创建一个对象的深拷贝。

注意:也许有人提到了对象解构运算,这也是浅拷贝。

JSON.parse

创建对象副本的最古老方法之一是:将该对象转换为其 JSON 字符串表示形式,然后将其解析回对象。这感觉有点压抑,但它确实有效:

const obj = /* ... */;
const copy = JSON.parse(JSON.stringify(obj));

这里的缺点是你创建一个临时的,可能很大的字符串,只是为了把它重新放回解析器。另一个缺点是这种方法不能处理循环对象。而且循环对象经常发生。例如,当您构建树状数据结构,其中一个节点引用其父级,而父级又引用其子级。

const x = {};
const y = {x};
x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)); // throws!

另外,诸如 Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型在进行序列化时会丢失。

Structured Clone 结构化克隆算法

Structured cloning 是一种现有的算法,用于将值从一个地方转移到另一地方。例如,每当您调用postMessage将消息发送到另一个窗口或 WebWorker 时,都会使用它。关于结构化克隆的好处在于它处理循环对象并 支持大量的内置类型。问题是,在编写本文时,该算法并不能直接使用,只能作为其他 API 的一部分。我想我们应该了解一下包含哪些,不是吗。。。

MessageChannel

正如我所说的,只要你调用postMessage结构化克隆算法就可以使用。我们可以创建一个 MessageChannel 并发送消息。在接收端,消息包含我们原始数据对象的结构化克隆。

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

const obj = /* ... */;
const clone = await structuralClone(obj);

这种方法的缺点是它是异步的。虽然这并无大碍,但是有时候你需要使用同步的方式来深度拷贝一个对象。

History API

如果你曾经使用history.pushState()写过 SPA,你就知道你可以提供一个状态对象来保存 URL。事实证明,这个状态对象使用结构化克隆 - 而且是同步的。我们必须小心使用,不要把程序逻辑使用的状态对象搞乱了,所以我们需要在完成克隆之后恢复原始状态。为了防止发生任何意外,请使用history.replaceState()而不是history.pushState()

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);
  return copy;
}

const obj = /* ... */;
const clone = structuralClone(obj);

然而,仅仅为了复制一个对象,而使用浏览器的引擎,感觉有点过分。另外,Safari 浏览器对replaceState调用的限制数量为 30 秒内 100 次。

Notification API

在发了一条推文之后,Jeremy Banks 向我展示了第三种方法来利用结构化克隆:Notification API。

function structuralClone(obj) {
  return new Notification('', {data: obj, silent: true}).data;
}

const obj = /* ... */;
const clone = structuralClone(obj);

短小,简洁。我喜欢它!

但是,它需要浏览器内部的权限机制,所以我怀疑它是很慢的。由于某种原因,Safari 总是返回undefined

따라서 원본 객체를 유지해야 하는 경우도 있습니다. 이 경우 함수가 원본 객체를 변경하는 것을 방지하기 위해 원본 객체의 복사본을 함수에 전달해야 합니다. 🎜

얕은 복사: Object.sign()

🎜객체의 복사본을 얻는 간단한 방법은 Object.asse(target,sources...)를 사용하는 것입니다. ) 코드>. 원하는 수의 소스 개체를 허용하고 모든 속성을 열거한 다음 <code>target에 할당합니다. 새로운 빈 개체 target을 사용하면 개체를 복사할 수 있습니다. 🎜rrreee🎜그러나 이것은 얕은 사본일 뿐입니다. 객체에 다른 객체가 속성으로 포함되어 있으면 공유 참조를 유지하게 되는데, 이는 우리가 원하는 것이 아닙니다. 🎜rrreee
Object.sign 메소드는 소스 객체 자체만 복사하고 대상 객체에 대한 열거 가능한 속성. 이 메서드는 소스 객체의 [[Get]] 및 대상 객체의 [[Set]]를 사용하므로 관련 getter 및 세터
. 따라서 단순히 새 속성을 복사하거나 정의하는 것이 아니라 속성을 할당합니다. 병합 소스에 getter가 포함되어 있으면 새 속성을 프로토타입에 병합하는 데 적합하지 않을 수 있습니다. 속성 정의(열거 가능성 포함)를 프로토타입에 복사하려면 Object.getOwnPropertyDescriptor()Object.defineProperty()를 사용하세요. 🎜그럼 이제 어떻게 될까요? 객체의 전체 복사본을 생성하는 방법에는 여러 가지가 있습니다. 🎜🎜참고: 누군가가 객체 파괴 작업을 언급했을 수도 있습니다. 이 작업 역시 얕은 복사본입니다. 🎜

JSON.parse

🎜객체의 복사본을 만드는 가장 오래된 방법 중 하나는 객체를 JSON 문자열 표현으로 변환한 다음 다시 객체로 구문 분석합니다. 약간 답답하게 느껴지지만 작동합니다. 🎜rrreee🎜 여기서 단점은 파서에 다시 넣기 위해 일시적이고 잠재적으로 큰 문자열을 생성한다는 것입니다. 또 다른 단점은 이 방법이 순환 객체를 처리할 수 없다는 것입니다. 그리고 객체 반복이 자주 발생합니다. 예를 들어, 트리와 같은 데이터 구조를 구축할 때 노드는 상위를 참조하고, 노드는 다시 하위를 참조합니다. 🎜rrreee🎜이외에도 Map, Set, RegExp, Date, ArrayBuffer code> 및 기타 내장 유형은 직렬화되면 손실됩니다. 🎜<h2>구조적 복제 구조적 복제 알고리즘</h2>🎜구조적 복제는 한 곳에서 다른 곳으로 값을 전송하는 데 사용되는 기존 알고리즘입니다. 예를 들어 postMessage를 호출하여 다른 창이나 WebWorker에 메시지를 보낼 때마다 사용됩니다. 구조적 복제의 좋은 점은 순환 객체를 처리하고 수많은 내장 유형을 지원한다는 것입니다. 문제는 이 글을 쓰는 시점에서 알고리즘을 직접 사용할 수 없고 다른 API의 일부로만 사용할 수 있다는 것입니다. 내 생각에 우리는 무엇이 포함되어 있는지 알아야 할 것 같아요. 그렇지 않나요? . . 🎜<h3>MessageChannel</h3>🎜 앞서 말했듯이 구조화된 복제 알고리즘은 <code>postMessage를 호출하는 한 작동합니다. MessageChannel을 만들고 메시지를 보낼 수 있습니다. 수신측에서는 메시지에 원본 데이터 개체의 구조화된 복제본이 포함되어 있습니다. 🎜rrreee🎜이 접근 방식의 단점은 비동기식이라는 점입니다. 괜찮지만 때로는 객체를 동기식으로 전체 복사해야 하는 경우도 있습니다. 🎜

History API

🎜history.pushState()를 사용하여 SPA를 작성한 적이 있다면 상태 개체를 제공하여 URL을 저장할 수 있다는 것을 알고 계실 것입니다. 강하다> . 이 상태 개체는 구조화된 복제를 사용하며 동기식이라는 것이 밝혀졌습니다. 프로그램 로직이 사용하는 상태 객체를 엉망으로 만들지 않도록 주의해야 하므로 복제 후에는 원래 상태를 복원해야 합니다. 예상치 못한 상황을 방지하려면 history.pushState() 대신 history.replaceState()를 사용하세요. 🎜rrreee🎜그러나 단지 개체를 복사하기 위해 브라우저 엔진을 사용하는 것은 약간 과잉으로 느껴집니다. 또한 Safari는 replaceState 호출 수를 30초당 100회로 제한합니다. 🎜

Notification API

🎜트윗 후 Jeremy Banks는 구조적 복제를 활용하는 세 번째 방법인 알림 API를 보여주었습니다. 🎜rrreee🎜짧고 간결합니다. 나는 그것을 좋아한다! 🎜🎜하지만 브라우저 내부의 권한 메커니즘이 필요하기 때문에 속도가 매우 느린 것으로 의심됩니다. 어떤 이유로 Safari는 항상 정의되지 않은을 반환합니다. 🎜

성능의 화려함

어떤 방식이 가장 성능이 좋은지 측정하고 싶습니다. 첫 번째 (순진한) 시도에서는 작은 JSON 개체를 가져와서 다른 방식으로 수천 번 복제했습니다. 다행히 Mathias Bynens는 객체에 속성을 추가할 때 V8에 캐시가 있다고 말했습니다. 그래서 캐시를 벤치마킹하고 있습니다. 캐시에 도달하지 않도록 하기 위해 임의의 키 이름을 사용하여 지정된 깊이와 너비의 객체를 생성하는 함수를 작성하고 테스트를 다시 실행했습니다.

차트!

Chrome, Firefox, Edge의 다양한 기술 성능은 다음과 같습니다. 낮을수록 좋습니다.

JavaScript 딥 카피 성능에 대한 깊은 이해

JavaScript 딥 카피 성능에 대한 깊은 이해

JavaScript 딥 카피 성능에 대한 깊은 이해

결론

그래서 우리는 여기서 무엇을 얻었나요?

  • 루프 개체가 없고 기본 제공 유형을 유지할 필요가 없는 경우 크로스 브라우저 JSON.parse(JSON.stringify())를 사용할 수 있습니다. 가장 빠른 복제 성능을 보여 매우 놀랐습니다. JSON.parse(JSON.stringify())获得最快的克隆性能,这让我感到非常惊讶。

  • 如果你想要一个适当的结构化克隆,MessageChannel是你唯一可靠的跨浏览器的选择。

如果浏览器平台直接提供一个 structuredClone()

올바르게 구조화된 복제본을 원한다면 MessageChannel이 유일하게 신뢰할 수 있는 크로스 브라우저 옵션입니다.

브라우저 플랫폼에서 structuredClone() 기능을 직접 제공하면 더 좋지 않을까요? 나는 확실히 그렇다고 생각합니다. 최신 HTML 사양에서는 이 동기 복제 = global.structuredClone(value, transfer = []) API · Issue #793 · whatwg/html에 대해 논의하고 있습니다.

관련 권장 사항:

jQuery의 $.extend 얕은 복사본 및 전체 복사본 예제 분석

jquery에서 전체 복사본 및 얕은 복사본 구현

🎜🎜Js의 얕은 복사본과 깊은 복사본이란 무엇입니까🎜🎜

위 내용은 JavaScript 딥 카피 성능에 대한 깊은 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.