본 글에서는 코드 분석 예시를 통해 자바스크립트 배열 연산의 어려움과 주의해야 할 사항을 함께 알아보고 참고하겠습니다.
다음 내용은 자바스크립트 배열을 배울 때의 경험과 주의할 점을 정리한 내용입니다.
배열을 탐색하기 위해 for_in을 사용하지 마세요
이것은 JavaScript 초보자들이 흔히 저지르는 오해입니다. for_in은 프로토타입 체인을 포함하여 객체의 모든 열거 가능한(열거 가능한) 키를 탐색하는 데 사용됩니다. 원래는 배열 탐색을 위해 존재하지 않습니다.
for_in을 사용하여 배열을 순회하는 데에는 세 가지 문제가 있습니다.
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, 정의되지 않음, Infinity로 변환됩니다. JSON에서는 JSON을 직렬화할 때 값이 null로 변환됩니다. 역직렬화 후에는 자연스럽게 null이 됩니다🎜🎜정의되지 않은 값이 있는 키-값 쌍은 손실됩니다.🎜🎜정의되지 않은 값이 있는 키는 JSON 직렬화 중에 무시되고 deserialization 당연히 돌아오면 없어집니다🎜🎜Date 객체는 문자열로 변환됩니다🎜🎜JSON은 객체 유형을 지원하지 않습니다. 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
는 첫 번째 정규화된 값을 반환합니다. 정규화된 값이 0인지 확인하려면 이 값을 직접 사용하여 if
를 수행하세요. 관리하다? 🎜🎜arr.find
는 배열에서 값을 찾은 후 추가로 처리하는 것입니다. 일반적으로 객체 배열의 경우에 사용됩니다. 존재; 둘 다 혼합되지 않습니다. 🎜🎜arr.forEach 대신 arr.map을 사용하지 마세요🎜🎜이 또한 JavaScript 초보자가 자주 저지르는 실수입니다. 그들은 종종 Array.prototype.map
과 를 구분하지 않습니다. Array.prototype.forEach 실제 의미. 🎜🎜<code>map
은 중국어로 map
이라고 합니다. 특정 시퀀스에 대해 특정 기능을 순차적으로 실행하여 또 다른 새로운 시퀀스를 파생합니다. 이 함수는 일반적으로 부작용이 없으며 원래 배열(소위 순수 함수)을 수정하지 않습니다. 🎜🎜forEach
에는 설명이 많지 않습니다. 단순히 특정 기능으로 배열의 모든 항목을 처리합니다. forEach
에는 반환 값이 없으므로(정의되지 않음 반환) 콜백 함수에는 일반적으로 부작용이 포함됩니다. 그렇지 않으면 이 forEach
는 의미가 없습니다. 🎜🎜map
이 forEach
보다 더 강력한 것은 사실이지만 map
은 새로운 배열을 생성하고 메모리를 차지합니다. map
의 반환 값을 사용하지 않는 경우 forEach
🎜🎜보충: 추가 경험을 사용해야 합니다. span>🎜 🎜ES6 이전에는 배열을 순회하는 두 가지 주요 방법이 있었습니다: 아래 첨자 반복을 사용하는 손으로 작성한 루프와 Array.prototype.forEach
. 전자는 다재다능하고 가장 효율적이지만 작성하기가 더 번거롭습니다. 배열의 값을 직접 얻을 수 없습니다. 🎜🎜저자는 개인적으로 후자를 좋아합니다. 반복의 첨자와 값, 기능적 스타일을 직접 얻을 수 있습니다(FP는 불변 데이터 구조에 초점을 맞추고 forEach는 본질적으로 부작용이므로 다음 형식만 가집니다. FP는 있지만 마법은 없습니다.) 쓰기에 매우 상쾌합니다. 하지만! 여러분 중 누구든지 눈치 채셨는지 궁금합니다. 각 작업을 일단 시작하면 멈출 수 없습니다. . . 🎜forEach는 콜백 함수를 허용하므로 미리 return
할 수 있습니다. 이는 직접 작성한 루프의 continue
와 같습니다. 하지만 중단
할 수는 없습니다. 콜백 함수에 중단
할 수 있는 루프가 없기 때문입니다.
[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 이전에는 이 방법을 주로 사용했습니다(사실 바벨 코드의 확장으로 인해 지금도 가끔 사용하고 있습니다). ES6는 다릅니다. for...of가 있습니다. for...of
는 실제 루프이므로 break
할 수 있습니다.rrreee🎜하지만 문제가 있습니다. for...of code> 루프의 첨자를 얻을 수 없는 것 같습니다. 실제로 JavaScript 언어 개발자는 이 문제를 생각하고 다음과 같이 해결할 수 있습니다: rrreee🎜Array.prototype.entries🎜<code>for...of
및 forEach
성능 테스트: https://jsperf.com/array-fore... Chrome의 for...of
가 더 빠릅니다.위 내용은 JavaScript 배열 작업의 어려움(자세한 튜토리얼)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!