◆ clone するときに JSON 化してパースするのより JavaScript で再帰的に処理するほうが早かった
◆ 複雑なオブジェクトで再帰処理が増えると差は縮まったのでデータが極端に大きいと JSON 化のほうが早くなるかも
◆ JSON ⇨ 準備いらずに使える
◆ JavaScript ⇨ 関数作るかコピーしてくる準備がいるけど速度が速くて Date 型などの例外を扱いやすい

JavaScript でオブジェクトをクローンしたいことってありますよね
言語自体でサポートされていないのでけっこう大変です

clone って JSON 表現できるもので十分

昔は完全にクローンしようと getter/setter や enumerable が false で for-in や Object.keys で取れないものも含めて オブジェクトや配列は再帰的に処理して 循環参照の場合はクローンしたものの中で循環参照するようにと いろいろ高機能なものも作りました

それでも ユーザが作った関数のインスタンスであるオブジェクトは暗黙的に見えないプロパティの参照を扱っていて単に新規オブジェクトにプロパティ設定とプロトタイプチェーンを同じにするだけでは動かないものも多いです
関数も同じでレキシカルスコープで外側の変数を参照していると 文字列化して文字列から新規に関数を作っても動かないです
関数はクローンせず同じ参照とすると呼び出すたびカウントアップする関数は片方が呼び出すと両方に影響するようになります
また関数自体はイミュータブルといってもオブジェクトでもあるのでプロパティを変更することは可能でそこは反映されます
他にも WeakMap 等に使われているとクローンされたものの参照は保存されてないので正しく動かないなど問題はいろいろあります

そういう事を考えていると 完全にクローンって無理だしそもそもそれが必要なケースってほとんどない気がしてきました
基本クローンするものって関数など処理する部分はなくて設定などを保存したただのデータです
単純なデータ構造のみで JSON 化できるようなデータのみです
一部特別な Date や RegExp はあるものの関数などはクローンする必要がないです
そういうことをしたい場合は特殊なので独自にそのときに必要な部分のみのクローン機能を作ればよくて汎用的な使い回すクローン関数は JSON で表現できるもので十分です
getter/setter くらいはあったほうがいいかもしれませんが getter があることもあまりないので対象外にします

クローン方法

そうなると作るのは簡単で 非オブジェクトならそのまま値を返して オブジェクトや配列なら再帰的に全部のキーをクローンするだけです
そうやって作ったのを使っていたのですが いくつかのライブラリのソースをみていたときに JSON 化+パースしてクローンしてるのを見かけました
JSON 化できるのだから本当に JSON 化してパースすればクローンされます
これらの関数は組み込まれているもので JavaScript じゃなくてネイティブな処理になってると思います
なら JavaScript で再帰的に処理するより早そうと思って比較してみました

比較

<!doctype html>

<div></div>
<script>
const clone1 = (value) => {
return JSON.parse(JSON.stringify(value))
}
const clone2 = (value) => {
switch (typeof value) {
case "number":
case "string":
case "boolean":
case "symbol":
case "undefined":
{
return value
}
break
case "object":
{
if (value === null) {
return value
} else if (Array.isArray(value)) {
return value.map(e => clone2(e))
} else {
const obj = {}
for (const [k, v] of Object.entries(value)) {
obj[k] = clone2(v)
}
return obj
}
}
break
}
}

// testdata
const values = [
"abcd",
"a/b".repeat(10000).split("/"),
]

{
const tmp = { a: 1, b: 2, c: 3, d: [1, 20.1, false, { x: { y: { z: { q: { y: { x: { u: { d: { h: 3, g: [], x: [{}, {}, null] } } } } }, v: 2 }, s: 3, y: [1, 2] } }, y: 10 }] }
tmp.f = clone1(tmp)
tmp.g = clone1(tmp)
tmp.k = clone1(tmp.d).concat(clone1(tmp.d)).concat(clone1(tmp.d))
tmp.u = clone1(tmp)
values.push(tmp)
}

// check
const json_0 = JSON.stringify(values[2])
const json_1 = JSON.stringify(clone1(values[2]))
const json_2 = JSON.stringify(clone2(values[2]))
console.log(json_0 === json_1 && json_0 === json_2)

// benchmark
for (const value of values) {
console.time("json")
for (let i = 0; i < 10000; i++) clone1(value)
console.timeEnd("json")
console.time("follow")
for (let i = 0; i < 10000; i++) clone2(value)
console.timeEnd("follow")
}

</script>

ただの文字列と長めの配列と大きめのオブジェクトの 3 種類試しました

結果は

true
json: 22ms
follow: 2ms
json: 11791.070068359375ms
follow: 3430ms
json: 1332ms
follow: 1075.999755859375ms

最初の true は値が同じことの確認です
json が JSON.stringify + JSON.parse の方です
follow が再帰的にプロパティをたどってる方です

JSON のほうが早いと思ってたのに意外と JavaScript での処理のほうが全部の場合で早かったです
単純な文字列だと再帰もせずただ値を返してるだけなのでこれは納得です
大きめな配列になると差は 3 倍程度まで縮まって複雑なオブジェクトでは 1.3 倍くらいまで縮まりました
やはり再帰呼び出しが多くなるほど遅くなるようです

もっとデータの多いデータベースのデータのダンプとかになると逆に JSON 化のほうが早くなるかもです
ただ一般的な設定ファイル程度なら JSON 化しないほうが早そうです
何万回と繰り返さないなら JSON 化のほうが準備いらずで楽ですが 速度が気になるなら自分で再帰してコピーするほうが良さそうです