ホームページ > 記事 > ウェブフロントエンド > JavaScript の配列操作の難しさ (詳細なチュートリアル)
この記事ではJavaScriptの配列操作の難しさや注意点をコード解析例を交えて解説していますので、一緒に学んで参考にしてみましょう。
以下の内容はJavaScriptの配列を学習する際の体験談と注意すべき点をまとめたものです。
配列の走査に for_in を使用しないでください
これは、JavaScript 初心者によくある誤解です。 for_in は、プロトタイプ チェーンを含むオブジェクト内のすべての列挙可能な (列挙可能な) キーを走査するために使用されます。これは、もともと配列を走査するために存在しません。
for_in を使用して配列を走査する場合には 3 つの問題があります:
1. 走査順序が固定されていません
JavaScript エンジンはオブジェクトの走査順序を保証しません。通常のオブジェクトとして配列を走査する場合、走査のインデックス順序も保証されません。
2. オブジェクト プロトタイプ チェーン上の値が走査されます。
enumerable: false
に設定せずに配列のプロトタイプ オブジェクト (ポリフィルなど) を変更すると、for_in はこれらのことを反復処理します。 enumerable: false
,for_in 会把这些东西遍历出来。
3、运行效率低下。
尽管理论上 JavaScript 使用对象的形式储存数组,JavaScript 引擎还是会对数组这一非常常用的内置对象特别优化。 https://jsperf.com/for-in-vs-...
可以看到使用 for_in 遍历数组要比使用下标遍历数组慢 50 倍以上
PS:你可能是想找 for_of
不要用 JSON.parse(JSON.stringify()) 深拷贝数组
有人使用 JSON 中深拷贝对象或数组。这虽然在多数情况是个简单方便的手段,但也可能引发未知 bug,因为:会使某些特定值转换为 null
NaN, undefined, Infinity 对于 JSON 中不支持的这些值,会在序列化 JSON 时被转换为 null,反序列化回来后自然也就是 null
会丢失值为 undefined 的键值对
JSON 序列化时会忽略值为 undefined 的 key,反序列化回来后自然也就丢失了
会将 Date 对象转换为字符串
JSON 不支持对象类型,对于 JS 中 Date 对象的处理方式为转换为 ISO8601 格式的字符串。然而反序列化并不会把时间格式的字符串转化为 Date 对象
运行效率低下。
作为原生函数,JSON.stringify
和 JSON.parse
自身操作 JSON 字符串的速度是很快的。然而为了深拷贝数组把对象序列化成 JSON 再反序列化回来完全没有必要。
我花了一些时间写了一个简单的深拷贝数组或对象的函数,测试发现运行速度差不多是使用 JSON 中转的 6 倍左右,顺便还支持了 TypedArray、RegExp 的对象的复制
https://jsperf.com/deep-clone...
不要用 arr.find 代替 arr.some
Array.prototype.find
是 ES2015 中新增的数组查找函数,与 Array.prototype.some
有相似之处,但不能替代后者。
Array.prototype.find
返回第一个符合条件的值,直接拿这个值做 if
判断是否存在,如果这个符合条件的值恰好是 0 怎么办?
arr.find
是找到数组中的值后对其进一步处理,一般用于对象数组的情况;arr.some
才是检查存在性;两者不可混用。
不要用 arr.map 代替 arr.forEach
也是一个 JavaScript 初学者常常犯的错误,他们往往并没有分清 Array.prototype.map
和 Array.prototype.forEach
的实际含义。
map
中文叫做 映射
,它通过将某个序列依次执行某个函数导出另一个新的序列。这个函数通常是不含副作用的,更不会修改原始的数组(所谓纯函数)。
forEach
就没有那么多说法,它就是简单的把数组中所有项都用某个函数处理一遍。由于 forEach
没有返回值(返回 undefined),所以它的回调函数通常是包含副作用的,否则这个 forEach
写了毫无意义。
确实 map
比 forEach
更加强大,但是 map
会创建一个新的数组,占用内存。如果你不用 map
的返回值,那你就应当使用 forEach
补:心得补充
ES6 以前,遍历数组主要就是两种方法:手写循环用下标迭代,使用 Array.prototype.forEach
3. 作業効率が低い。
🎜🎜理論的には、JavaScript は配列を格納するためにオブジェクトの形式を使用しますが、JavaScript エンジンは、非常に一般的に使用される組み込みオブジェクトである配列に対して特に最適化されています。 https://jsperf.com/for-in-vs-...null
に変換されます🎜🎜NaN、未定義、サポートされていないものについては無限大JSON では、JSON をシリアル化すると、値は null に変換されます。逆シリアル化後は、当然 null になります🎜🎜未定義の値を持つキーと値のペアは失われます🎜🎜未定義の値を持つキーは、JSON シリアル化中に無視されます。 deserialization 戻ってきたら当然失われます🎜🎜Dateオブジェクトは文字列に変換されます🎜🎜JSにおけるDateオブジェクトの処理方法は、ISO8601形式の文字列に変換することです。ただし、逆シリアル化では時刻形式文字列が Date オブジェクトに変換されないため、非効率的です。 🎜🎜ネイティブ関数として、JSON.stringify
と JSON.parse
は JSON 文字列を非常に高速に操作します。ただし、配列をディープ コピーするために、オブジェクトを JSON にシリアル化し、逆シリアル化して戻す必要はまったくありません。 🎜🎜配列またはオブジェクトをディープコピーするための簡単な関数を作成するのに時間を費やしましたが、テストの結果、実行速度が JSON 転送を使用する場合のほぼ 6 倍であることがわかりました。ちなみに、TypedArray オブジェクトと RegExp オブジェクトのコピーもサポートしています。 https://jsperf.com/deep-clone...🎜🎜 arr.some の代わりに arr.find を使用しないでください🎜🎜Array.prototype.find
は ES2015 の新しい配列検索関数ですこれは Array.prototype.some
と同じですが、後者に代わるものではありません。 🎜🎜Array.prototype.find
は最初の修飾された値を返します。この値を直接使用して if
を実行して、それが存在するかどうかを判断します。修飾された値がたまたま 0 だった場合はどうなるでしょうか?管理? 🎜🎜arr.find
は、配列内の値を見つけた後、その値をさらに処理するために使用されます。arr.some
は、オブジェクト配列の場合に使用されます。両方の存在を混在させないでください。 🎜🎜 arr.forEach の代わりに arr.map を使用しないでください🎜🎜 これは、JavaScript 初心者がよく犯す間違いでもあり、Array.prototype.map
と を区別していないことがよくあります。 Array.prototype.forEach の実際の意味。 🎜🎜<code>map
は中国語で map
と呼ばれ、特定のシーケンスに対して特定の関数を順番に実行することで、別の新しいシーケンスを導き出します。通常、この関数には副作用はなく、元の配列は変更されません (いわゆる純粋関数)。 🎜🎜forEach
には多くの説明はありませんが、配列内のすべての項目を特定の関数で処理するだけです。 forEach
には戻り値がない (未定義を返す) ため、そのコールバック関数には通常、副作用が含まれます。それ以外の場合、この forEach
は無意味です。 🎜🎜map
が forEach
より強力であるのは事実ですが、map
は新しい配列を作成してメモリを占有します。 map
の戻り値を使用しない場合は、forEach
を使用する必要があります🎜🎜補足: 追加のエクスペリエンス steam>🎜 🎜ES6 より前には、配列を走査するための主な方法が 2 つありました。それは、添字反復を使用した手書きループと Array.prototype.forEach
です。前者は多用途で最も効率的ですが、配列内の値を直接取得できないため、記述がより面倒になります。 🎜🎜著者は個人的に後者が好きです。反復の添字と値、および関数スタイルを直接取得できます (FP は不変のデータ構造に焦点を当てており、forEach は本質的に副作用であるため、次の形式のみであることに注意してください) FP ですが魔法はありません)を書くのは非常に新鮮です。しかし!学生の皆さんは気づいたでしょうか。forEach は一度始めると止まらないのです。 。 。 🎜forEach はコールバック関数を受け入れます。事前に return
することができます。これは、手書きループの continue
と同等です。しかし、break
することはできません。コールバック関数には break
するためのループがないからです。
[1, 2, 3, 4, 5].forEach(x => { console.log(x); if (x === 3) { break; // SyntaxError: Illegal break statement } });
return
,相当于手写循环中的 continue
。但是你不能 break
——因为回调函数中没有循环让你去 break
:try { [1, 2, 3, 4, 5].forEach(x => { console.log(x); if (x === 3) { throw 'break'; } }); } catch (e) { if (e !== 'break') throw e; // 不要勿吞异常。。。 }
解决方案还是有的。其他函数式编程语言例如 scala
就遇到了类似问题,它提供了一个函数
break,作用是抛出一个异常。
我们可以仿照这样的做法,来实现 arr.forEach
的 break
:
[1, 2, 3, 4, 5].some(x => { console.log(x); if (x === 3) { return true; // break } // return undefined; 相当于 false });
还有其他方法,比如用 Array.prototype.some
代替 Array.prototype.forEach
。
考虑 Array.prototype.some 的特性,当 some
找到一个符合条件的值(回调函数返回 true
)时会立即终止循环,利用这样的特性可以模拟 break
:
for (const x of [1, 2, 3, 4, 5]) { console.log(x); if (x === 3) { break; } }
some
的返回值被忽略掉了,它已经脱离了判断数组中是否有元素符合给出的条件这一原始的含义。
在 ES6 前,笔者主要使用该法(其实因为 Babel 代码膨胀的缘故,现在也偶尔使用),ES6 不一样了,我们有了 for...of。for...of
是真正的循环,可以 break
:
for (const [index, value] of [1, 2, 3, 4, 5].entries()) { console.log(`arr[${index}] = ${value}`); }
但是有个问题,for...of
似乎拿不到循环的下标。其实 JavaScript 语言制定者想到了这个问题,可以如下解决:
Array.prototype.entries
for...of
和 forEach
的性能测试:https://jsperf.com/array-fore... Chrome 中 for...of
まだ解決策はあります。 scala
などの他の関数型プログラミング言語でも、例外をスローする関数
break が提供されており、同様の問題が発生しています。
arr.forEach
の break
を実装できます:rrreee🎜Array.prototype.some <code>Array.prototype.forEach
を置き換えます。 🎜 Array.prototype.some の特性を考慮してください。some
が条件を満たす値を見つけた場合 (コールバック関数は true
を返します)、ループは次のようになります。これを使用してください。 break
の特性をシミュレートできます:rrreee🎜 some
の戻り値は無視され、指定された条件を満たす要素が配列内にあるかどうか。 🎜 ES6 より前は、主にこの方法を使用していました (実際、Babel コードの拡張により、現在は ES6 とは異なり、...of 用に使用しています)。 for...of
は実際のループです。break
を実行できます:rrreee🎜しかし、for...of
には問題があります。 code> ループの添字が取得できないようです。実際、JavaScript 言語開発者はこの問題を考え、次のように解決できます。rrreee🎜Array.prototype.entries🎜for...of
および forEach
パフォーマンス テスト: https://jsperf.com/array-fore... Chrome の for...of
の方が高速です以上がJavaScript の配列操作の難しさ (詳細なチュートリアル)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。