ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript ディープコピーのパフォーマンスについての深い理解
この記事では主に 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]]
,所以它会调用相关getter
和setter
。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含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 cloning 是一种现有的算法,用于将值从一个地方转移到另一地方。例如,每当您调用postMessage将消息发送到另一个窗口或 WebWorker 时,都会使用它。关于结构化克隆的好处在于它处理循环对象并 支持大量的内置类型。问题是,在编写本文时,该算法并不能直接使用,只能作为其他 API 的一部分。我想我们应该了解一下包含哪些,不是吗。。。
正如我所说的,只要你调用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.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 次。
在发了一条推文之后,Jeremy Banks 向我展示了第三种方法来利用结构化克隆:Notification API。
function structuralClone(obj) { return new Notification('', {data: obj, silent: true}).data; } const obj = /* ... */; const clone = structuralClone(obj);
短小,简洁。我喜欢它!
但是,它需要浏览器内部的权限机制,所以我怀疑它是很慢的。由于某种原因,Safari 总是返回undefined
Object.assign()
Object.assign(target,sources... を使用することです) )コード>。任意の数のソース オブジェクトを受け入れ、そのすべてのプロパティを列挙し、それらを <code>target
に割り当てます。新しい空のオブジェクト target
を使用すると、そのオブジェクトをコピーできます。 🎜rrreee🎜 ただし、 これは浅いコピーにすぎません。オブジェクトに他のオブジェクトがプロパティとして含まれている場合、それらは共有参照を保持しますが、これは私たちが望んでいることではありません: 🎜rrreee🎜それではどうなるでしょうか?オブジェクトのディープ コピーを作成するには、いくつかの方法があります。 🎜🎜注: おそらく誰かがオブジェクトの構造化操作について言及しましたが、これも浅いコピーです。 🎜Object.assign
メソッドはソース オブジェクト自体をコピーするだけであり、列挙可能ですターゲット オブジェクトにプロパティを追加します。このメソッドは、ソース オブジェクトの[[Get]]
とターゲット オブジェクトの[[Set]]
を使用するため、関連するgetter
と セッターコード>。したがって、単に新しいプロパティをコピーまたは定義するのではなく、プロパティを割り当てます。マージ ソースにgetter
が含まれている場合、新しいプロパティをプロトタイプにマージするのに適さない可能性があります。プロパティ定義 (その列挙可能性を含む) をプロトタイプにコピーするには、Object.getOwnPropertyDescriptor()
とObject.defineProperty()
を使用します。
JSON.parse
JSON
文字列表現に変換してから、それを解析してオブジェクトに戻します。これは少し圧迫的に感じますが、うまくいきます: 🎜rrreee🎜 ここでの欠点は、パーサーに戻すためだけに一時的な、潜在的に大きな文字列を作成することです。もう 1 つの欠点は、このメソッドでは循環オブジェクトを処理できないことです。また、オブジェクトのループが頻繁に発生します。たとえば、ツリー状のデータ構造を構築する場合、ノードはその親を参照し、さらにその親がその子を参照します。 🎜rrreee🎜さらに、Map
、Set
、RegExp
、Date
、ArrayBuffer などcode> およびその他の組み込み型はシリアル化時に失われます。 🎜<h2>構造化クローン 構造化クローン作成アルゴリズム</h2>🎜 構造化クローンは、値をある場所から別の場所に転送するために使用される既存のアルゴリズムです。たとえば、別のウィンドウまたは WebWorker にメッセージを送信するために postMessage を呼び出すたびに使用されます。構造化クローン作成の優れた点は、循環オブジェクトを処理し、多数の組み込み型をサポートしていることです。問題は、この記事の執筆時点では、このアルゴリズムは直接利用できず、他の API の一部としてのみ利用できることです。何が含まれているかを知る必要があると思います。 。 。 🎜<h3>MessageChannel</h3>🎜 先ほども述べたように、構造化クローン作成アルゴリズムは、<code>postMessage
を呼び出している限り機能します。 MessageChannel
を作成してメッセージを送信できます。受信側では、メッセージには元のデータ オブジェクトの構造化されたクローンが含まれています。 🎜rrreee🎜 このアプローチの欠点は、非同期であることです。これは問題ありませんが、オブジェクトを同期的にディープ コピーする必要がある場合があります。 🎜unknown
を返します。 🎜どの方法が最もパフォーマンスが高いかを測定したいと思います。最初の (単純な) 試みでは、小さな JSON オブジェクトを取得し、そのオブジェクトをさまざまな方法で 1,000 回複製しました。幸いなことに、Mathias Bynens は、オブジェクトにプロパティを追加するときに V8 にはキャッシュがあると教えてくれました。そこでキャッシュのベンチマークを行っています。キャッシュにヒットしないようにするために、ランダムなキー名で指定された深さと幅のオブジェクトを生成する関数を作成し、テストを再実行しました。
ここでは、Chrome、Firefox、Edge のさまざまなテクノロジーのパフォーマンスを示します。低いほど良いです。
それで、ここから何が得られたでしょうか?
ループ オブジェクトがなく、組み込み型を保持する必要がない場合は、クロスブラウザーの JSON.parse(JSON.stringify())
を使用できます。クローン作成のパフォーマンスが最も速く、非常に驚きました。 JSON.parse(JSON.stringify())
获得最快的克隆性能,这让我感到非常惊讶。
如果你想要一个适当的结构化克隆,MessageChannel
是你唯一可靠的跨浏览器的选择。
如果浏览器平台直接提供一个 structuredClone()
適切に構造化されたクローンが必要な場合は、MessageChannel
が唯一の信頼できるクロスブラウザー オプションです。
structurdClone()
関数を直接提供した方がよいでしょうか?確かにそう思います。最新の HTML 仕様では、この Synchronous clone = global.structurdClone(value, transfer = []) API · Issue #793 · whatwg/html について議論しています。
jQuery の $.extend 浅いコピーと深いコピーの例の分析
🎜🎜 Js の浅いコピーと深いコピーとは何ですか🎜🎜以上がJavaScript ディープコピーのパフォーマンスについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。