diff --git a/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js b/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js index 024d6d6c2b..490f570ada 100644 --- a/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js @@ -1,8 +1,10 @@ function camelize(str) { return str - .split('-') // my-long-word -> ['my', 'long', 'word'] - .map( + .split('-') // splits 'my-long-word' into array ['my', 'long', 'word'] + .map( + // capitalizes first letters of all array items except the first one + // converts ['my', 'long', 'word'] into ['my', 'Long', 'Word'] (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1) - ) // ['my', 'long', 'word'] -> ['my', 'Long', 'Word'] - .join(''); // ['my', 'Long', 'Word'] -> myLongWord + ) + .join(''); // joins ['my', 'Long', 'Word'] into 'myLongWord' } diff --git a/1-js/05-data-types/05-array-methods/1-camelcase/task.md b/1-js/05-data-types/05-array-methods/1-camelcase/task.md index 941d9c785d..764ef116b6 100644 --- a/1-js/05-data-types/05-array-methods/1-camelcase/task.md +++ b/1-js/05-data-types/05-array-methods/1-camelcase/task.md @@ -4,11 +4,11 @@ importance: 5 # 将 border-left-width 转换成 borderLeftWidth -写函数 `camelize(str)` 将诸如 "my-short-string" 之类的由短划线分隔的单词变成骆驼式的 "myShortString"。 +编写函数 `camelize(str)` 将诸如 "my-short-string" 之类的由短划线分隔的单词变成骆驼式的 "myShortString"。 -即:删除所有短横线,短横线后的每一个单词变为首字母大写。 +即:删除所有短横线,并将短横线后的每一个单词的首字母变为大写。 -例如: +示例: ```js camelize("background-color") == 'backgroundColor'; @@ -16,4 +16,4 @@ camelize("list-style-image") == 'listStyleImage'; camelize("-webkit-transition") == 'WebkitTransition'; ``` -提示:使用 `split` 将字符串拆分成数组,然后将其转换 `join` 并返回。 +提示:使用 `split` 将字符串拆分成数组,对其进行转换之后再 `join` 回来。 diff --git a/1-js/05-data-types/05-array-methods/10-average-age/task.md b/1-js/05-data-types/05-array-methods/10-average-age/task.md index 5ef6491bb1..e182167e6b 100644 --- a/1-js/05-data-types/05-array-methods/10-average-age/task.md +++ b/1-js/05-data-types/05-array-methods/10-average-age/task.md @@ -2,11 +2,11 @@ importance: 4 --- -# 获取平均 +# 获取平均年龄 -编写 `getAverageAge(users)` 函数,该函数获取一个具有 age 属性的对象数组,并获取平均值。 +编写 `getAverageAge(users)` 函数,该函数获取一个具有 `age` 属性的对象数组,并返回平均年龄。 -平均的公式是 `(age1 + age2 + ... + ageN) / N`。 +平均值的计算公式是 `(age1 + age2 + ... + ageN) / N`。 例如: @@ -19,4 +19,3 @@ let arr = [ john, pete, mary ]; alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28 ``` - diff --git a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md index 65995830dc..a6f68a5124 100644 --- a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md +++ b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md @@ -1,8 +1,8 @@ -遍历数组 +让我们先遍历数字: - 对于每个元素,我们将检查结果数组是否已经有该元素。 -- 如果有,则忽略,否则添加结果。 +- 如果有,则忽略,否则将其添加到结果中。 -```js run +```js run demo function unique(arr) { let result = []; @@ -24,16 +24,16 @@ alert( unique(strings) ); // Hare, Krishna, :-O 代码有效,但其中存在潜在的性能问题。 -方法 `result.includes(str)` 在内部遍历数组 `result` 并将每个元素与 `str` 进行比较以找到匹配项。 +方法 `result.includes(str)` 在内部遍历数组 `result`,并将每个元素与 `str` 进行比较以找到匹配项。 -所以如果 `result` 中有 `100` 个元素,并且没有一个匹配上 `str`,那么它将遍历整个 `result` 并进行完全的 `100` 比较。如果 `result` 很大,比如 `10000`,那么会有 `10000` 次的比较。 +所以如果 `result` 中有 `100` 个元素,并且没有任何一项与 `str` 匹配,那么它将遍历整个 `result` 并进行 `100` 次比较。如果 `result` 很大,比如 `10000`,那么就会有 `10000` 次的比较。 -这本身并不是问题,因为 JavaScript 引擎速度非常快,所以遍历 10000 次就是几微秒的事。 +这本身并不是问题,因为 JavaScript 引擎速度非常快,所以遍历一个有 `10000` 个元素的数组只需要几微秒。 -但是我们在 `for `循环中为 `arr` 的每个元素做了这样的测试。 +但是我们在 `for `循环中对 `arr` 的每个元素都进行了一次检测。 -所以如果 `arr.length` 是 `10000`,我们会有 `10000 * 10000` = 100 百万的比较。好多啊。 +因此,如果 `arr.length` 是 `10000`,我们会有 `10000 * 10000` = 1 亿次的比较。那真的太多了。 所以该解决方案仅适用于小型数组。 -进一步,在 我们将看到如何优化它。 +进一步,在后面的 一章中,我们将看到如何对该方法进行优化。 diff --git a/1-js/05-data-types/05-array-methods/11-array-unique/task.md b/1-js/05-data-types/05-array-methods/11-array-unique/task.md index 4d5cff26e6..b21a97a13a 100644 --- a/1-js/05-data-types/05-array-methods/11-array-unique/task.md +++ b/1-js/05-data-types/05-array-methods/11-array-unique/task.md @@ -4,9 +4,9 @@ importance: 4 # 数组去重 -`arr` 是一个数组 +`arr` 是一个数组。 -创建一个函数 `unique(arr)`,返回去除重复元素的 arr。 +创建一个函数 `unique(arr)`,返回去除重复元素后的数组 `arr`。 例如: diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md index 06fed6a41a..9dfa92be11 100644 --- a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -4,7 +4,7 @@ importance: 4 # 从数组创建键(值)对象 -假设我们有以下形式的用户数组 `{id:..., name:..., age... }`。 +假设我们收到了一个用户数组,形式为:`{id:..., name:..., age... }`。 创建一个函数 `groupById(arr)` 从该数组创建对象,以 `id` 为键(key),数组项为值。 @@ -20,7 +20,7 @@ let users = [ let usersById = groupById(users); /* -// 调用方法后我们得到: +// 调用函数后我们得到: usersById = { john: {id: 'john', name: "John Smith", age: 20} @@ -31,8 +31,8 @@ usersById = { ``` -处理服务端数据时,此功能很有用。 +处理服务端数据时,这个函数很有用。 -在这个任务里我们假设 `id` 是唯一的。没有哪两个数组项具有相同的 `id` 。 +在这个任务里我们假设 `id` 是唯一的。没有两个具有相同 `id` 的数组项。 -请使用数组 `.reduce` 方法解决。 +请在解决方案中使用数组的 `.reduce` 方法。 diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/solution.md b/1-js/05-data-types/05-array-methods/2-filter-range/solution.md index e69de29bb2..73993a07a0 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/solution.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/solution.md @@ -0,0 +1,14 @@ +```js run demo +function filterRange(arr, a, b) { + // added brackets around the expression for better readability + return arr.filter(item => (a <= item && item <= b)); +} + +let arr = [5, 3, 8, 1]; + +let filtered = filterRange(arr, 1, 4); + +alert( filtered ); // 3,1 (matching values) + +alert( arr ); // 5,3,8,1 (not modified) +``` diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/task.md b/1-js/05-data-types/05-array-methods/2-filter-range/task.md index 6350e3fd16..47291bfcf6 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/task.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/task.md @@ -4,7 +4,7 @@ importance: 4 # 过滤范围 -写一个函数 `filterRange(arr, a, b)` 获取一个数组 `arr`,查找 `a` 和 `b` 之间的元素并返回它们的数组。 +写一个函数 `filterRange(arr, a, b)`,该函数获取一个数组 `arr`,在其中查找数值大小在 `a` 和 `b` 之间的元素,并返回它们的数组。 该函数不应该修改原数组。它应该返回新的数组。 diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js index 61cda126b6..488db3755b 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js @@ -1,5 +1,4 @@ - function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { @@ -12,4 +11,4 @@ function filterRangeInPlace(arr, a, b) { } } -} \ No newline at end of file +} diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md index e69de29bb2..36e3130ff0 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md @@ -0,0 +1,21 @@ +```js run demo +function filterRangeInPlace(arr, a, b) { + + for (let i = 0; i < arr.length; i++) { + let val = arr[i]; + + // remove if outside of the interval + if (val < a || val > b) { + arr.splice(i, 1); + i--; + } + } + +} + +let arr = [5, 3, 8, 1]; + +filterRangeInPlace(arr, 1, 4); // removed the numbers except from 1 to 4 + +alert( arr ); // [3, 1] +``` diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md index 9a9f308c6b..c12c7de2e9 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/task.md @@ -2,17 +2,17 @@ importance: 4 --- -# 范围过滤 +# 原位(in place)过滤范围 -写一个函数 `filterRangeInPlace(arr, a, b)` 获取一个数组 `arr`,并从中除去 `a` 和 `b` 区间以外的所有值。测试:`a ≤ arr[i] ≤ b`。 +写一个函数 `filterRangeInPlace(arr, a, b)`,该函数获取一个数组 `arr`,并删除其中介于 `a` 和 `b` 区间以外的所有值。检测:`a ≤ arr[i] ≤ b`。 -该函数只应修改数组。它不应该返回任何东西。 +该函数应该只修改数组。它不应该返回任何东西。 例如: ```js let arr = [5, 3, 8, 1]; -filterRangeInPlace(arr, 1, 4); // 删除了从 1 到 4 之外的数字 +filterRangeInPlace(arr, 1, 4); // 删除了范围在 1 到 4 之外的所有值 alert( arr ); // [3, 1] ``` diff --git a/1-js/05-data-types/05-array-methods/4-sort-back/task.md b/1-js/05-data-types/05-array-methods/4-sort-back/task.md index 2bce993342..b1426278bc 100644 --- a/1-js/05-data-types/05-array-methods/4-sort-back/task.md +++ b/1-js/05-data-types/05-array-methods/4-sort-back/task.md @@ -2,12 +2,13 @@ importance: 4 --- -# 倒序 +# 降序排列 ```js let arr = [5, 2, 1, -10, 8]; -// ... 倒序 +// ……你的代码以降序对其进行排序 + alert( arr ); // 8, 5, 2, 1, -10 ``` diff --git a/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md b/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md index cf285d41ca..1350b099af 100644 --- a/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md +++ b/1-js/05-data-types/05-array-methods/5-copy-sort-array/task.md @@ -13,6 +13,6 @@ let arr = ["HTML", "JavaScript", "CSS"]; let sorted = copySorted(arr); -alert( sorted ); // CSS,HTML,JavaScript -alert( arr ); // HTML,JavaScript,CSS(没有变化) +alert( sorted ); // CSS, HTML, JavaScript +alert( arr ); // HTML, JavaScript, CSS (no changes) ``` diff --git a/1-js/05-data-types/05-array-methods/6-array-get-names/task.md b/1-js/05-data-types/05-array-methods/6-array-get-names/task.md index 659181f812..9aa85c3aee 100644 --- a/1-js/05-data-types/05-array-methods/6-array-get-names/task.md +++ b/1-js/05-data-types/05-array-methods/6-array-get-names/task.md @@ -4,7 +4,7 @@ importance: 5 # 映射到 names -你有一个 `user` 对象数组,每个对象都有 `user.name `。编写将其转换为 names 数组的代码。 +你有一个 `user` 对象数组,每个对象都有 `user.name`。编写将其转换为 names 数组的代码。 例如: diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md b/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md index 6c4a165b76..e39d639b55 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md @@ -1,3 +1,3 @@ -- 请注意如何存储方法。它们只是被添加到 `this.methods` 属性中。 -- 所有测试和数值转换都在 `calculate` 方法中完成。将来它可能会扩展到支持更复杂的表达式。 +- 请注意方法的存储方式。它们只是被添加到 `this.methods` 属性中。 +- 所有检测和数字转换都通过 `calculate` 方法完成。将来可能会扩展它以支持更复杂的表达式。 diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md b/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md index d344d3b1a7..85e2c16ccf 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md @@ -4,11 +4,11 @@ importance: 5 # 创建一个可扩展的 calculator -创建一个构造函数 `Calculator`,创建可扩展的 calculator 对象。 +创建一个构造函数 `Calculator`,以创建“可扩展”的 calculator 对象。 该任务由两部分组成。 -1. 首先,实现 `calculate(str)` 方法,接受像 `"1 + 2"` 这样格式为“数字 运算符 数字”(以空格分隔)的字符串,并返回结果。该方法需要能够理解加号 `+` 和减号 `-`。 +1. 首先,实现 `calculate(str)` 方法,该方法接受像 `"1 + 2"` 这样格式为“数字 运算符 数字”(以空格分隔)的字符串,并返回结果。该方法需要能够理解加号 `+` 和减号 `-`。 用法示例: @@ -17,7 +17,7 @@ importance: 5 alert( calc.calculate("3 + 7") ); // 10 ``` -2. 然后添加 calculator 的新操作方法 `addOperator(name, func)`。它需要运算符 `name` 和实现它的双参数函数 `func(a,b)`。 +2. 然后添加方法 `addMethod(name, func)`,该方法教 calculator 进行新操作。它需要运算符 `name` 和实现它的双参数函数 `func(a,b)`。 例如,我们添加乘法 `*`,除法 `/` 和求幂 `**`: @@ -32,5 +32,5 @@ importance: 5 ``` - 此任务中没有括号或复杂的表达式。 -- 数字和运算符用一个空格分隔。 -- 你可以自行选择是否添加错误处理。 +- 数字和运算符之间只有一个空格。 +- 你可以自行选择是否添加错误处理功能。 diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md index 614da1601b..613a08d6ac 100644 --- a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md @@ -35,9 +35,9 @@ let usersMapped = users.map(user => *!*{*/!* }); ``` -我们记得,有两个箭头函数写法:直接返回值`value => expr` 和带主体的 `value => {...}`。 +我们记得,有两种箭头函数的写法:直接返回值 `value => expr` 和带主体的 `value => {...}`。 -JavaScript 会把 `{` 作为函数体的开始,而不是对象的开始。解决方法是将它们包装在正常括号中: +JavaScript 在这里会把 `{` 视为函数体的开始,而不是对象的开始。解决方法是将它们包装在普通括号 `()` 中: ```js let usersMapped = users.map(user => *!*({*/!* @@ -46,6 +46,6 @@ let usersMapped = users.map(user => *!*({*/!* })); ``` -现在就好了。 +这样就可以了。 diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/task.md b/1-js/05-data-types/05-array-methods/7-map-objects/task.md index 09b97e606c..5f3ad254fa 100644 --- a/1-js/05-data-types/05-array-methods/7-map-objects/task.md +++ b/1-js/05-data-types/05-array-methods/7-map-objects/task.md @@ -6,7 +6,7 @@ importance: 5 你有一个 `user` 对象数组,每个对象都有 `name`,`surname` 和 `id`。 -编写代码以从中创建另一个具有 `id` 和 `fullName` 的对象,其中 `fullName` 由 `name` 和 `surname` 生成。 +编写代码以该数组为基础,创建另一个具有 `id` 和 `fullName` 的对象数组,其中 `fullName` 由 `name` 和 `surname` 生成。 例如: @@ -33,4 +33,4 @@ alert( usersMapped[0].id ) // 1 alert( usersMapped[0].fullName ) // John Smith ``` -所以,实际上你需要将一个对象数组映射到另一个对象数组。可以尝试使用箭头函数来编写。 +所以,实际上你需要将一个对象数组映射到另一个对象数组。在这儿尝试使用箭头函数 `=>` 来编写。 diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md index 09e6634cc2..297c49c0b2 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md @@ -11,8 +11,24 @@ let arr = [ pete, john, mary ]; sortByAge(arr); -// 现在排序是:[john, mary, pete] +// 排序后的数组为:[john, mary, pete] alert(arr[0].name); // John alert(arr[1].name); // Mary alert(arr[2].name); // Pete ``` + +译注:解决方案的代码还可以更短一些 + +```js +function sortByAge(arr) { + arr.sort((a, b) => a.age - b.age); +} +``` + +因为 `sort()` 方法的语法为 `arr.sort([compareFunction])`,如果没有指明 `compareFunction`,那么元素会被按照转换为的字符串的诸个字符的 Unicode 编码进行排序,如果指明了 `compareFunction`,那么数组会按照调用该函数的返回值排序。即 `a` 和 `b` 是两个将要被比较的元素: + +- 如果 `compareFunction(a, b)` 小于 `0`,那么 `a` 会被排列到 `b` 之前; +- 如果 `compareFunction(a, b)` 等于 `0`,那么 `a` 和 `b` 的相对位置不变。备注:ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本); +- 如果 `compareFunction(a, b)` 大于 `0`,那么 `b` 会被排列到 `a` 之前。 + +因此,升序排列的函数可以简写为:`(a, b) => a.age - b.age`。 diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md index 6384817ddb..05a6e17840 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md @@ -4,7 +4,7 @@ importance: 5 # 按年龄对用户排序 -编写函数 `sortByAge(users)` 获得对象数组的 `age` 属性并对它进行排序。 +编写函数 `sortByAge(users)` 获得对象数组的 `age` 属性,并根据 `age` 对这些对象数组进行排序。 例如: diff --git a/1-js/05-data-types/05-array-methods/9-shuffle/solution.md b/1-js/05-data-types/05-array-methods/9-shuffle/solution.md index bd45d06926..870782017e 100644 --- a/1-js/05-data-types/05-array-methods/9-shuffle/solution.md +++ b/1-js/05-data-types/05-array-methods/9-shuffle/solution.md @@ -12,18 +12,18 @@ shuffle(arr); alert(arr); ``` -这样是可以的,因为 `Math.random() - 0.5` 是一个可能是正数或负数的随机数,所以排序函数会随机地重新排序元素。 +这样是可以的,因为 `Math.random() - 0.5` 是一个可能是正数或负数的随机数,因此排序函数会随机地对数组中的元素进行重新排序。 -但是因为排序函数并不意味着以这种方式使用,所以并不是所有的排列都具有相同的概率。 +但是,由于排序函数并非旨在以这种方式使用,因此并非所有的排列都具有相同的概率。 -例如,请考虑下面的代码。它运行 100 万次 `shuffle` 并计算所有可能结果: +例如,请考虑下面的代码。它运行 100 万次 `shuffle` 并计算所有可能结果的出现次数: ```js run function shuffle(array) { array.sort(() => Math.random() - 0.5); } -// 出现所有可能排列的次数 +// 所有可能排列的出现次数 let count = { '123': 0, '132': 0, @@ -39,13 +39,13 @@ for (let i = 0; i < 1000000; i++) { count[array.join('')]++; } -// 显示所有可能的排列的数量 +// 显示所有可能排列的出现次数 for (let key in count) { alert(`${key}: ${count[key]}`); } ``` -示例结果(V8,2017年七月): +示例结果(取决于 Javascript 引擎): ```js 123: 250706 @@ -56,24 +56,30 @@ for (let key in count) { 321: 125223 ``` -我们可以清楚地看到这种偏见:`123` 和 `213` 比其他人更频繁出现。 +我们可以清楚地看到这种倾斜:`123` 和 `213` 的出现频率比其他情况高得多。 -JavaScript 引擎的代码结果可能会有所不同,但我们已经可以看到这种方法是不可靠的。 +使用不同的 JavaScript 引擎运行这个示例代码得到的结果可能会有所不同,但是我们已经可以看到这种方法是不可靠的。 -为什么它不起作用?一般来说,`sort` 是一个“黑匣子”:我们向其中抛出一个数组和一个比较函数,并期望数组被排序。由于比较的完全随机性,黑盒子变得复杂,它究竟发生了什么取决于引擎之间的具体实现。 +为什么它不起作用?一般来说,`sort` 是一个“黑匣子”:我们将一个数组和一个比较函数放入其中,并期望其对数组进行排序。但是由于比较的完全随机性,这个黑匣子疯了,它发疯地确切程度取决于引擎中的具体实现方法。 -还有其他很好的方法来完成这项任务。例如,有一个很好的算法叫做 [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)。其思路是:逆向遍历数组,并将每个子项与前面随机的一个子项互相交换: +还有其他很好的方法可以完成这项任务。例如,有一个很棒的算法叫作 [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)。其思路是:逆向遍历数组,并将每个元素与其前面的随机的一个元素互换位置: ```js function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // 从 0 到 i 的随机索引 - [array[i], array[j]] = [array[j], array[i]]; // 交换元素 + + // 交换元素 array[i] 和 array[j] + // 我们使用“解构分配(destructuring assignment)”语法来实现它 + // 你将在后面的章节中找到有关该语法的更多详细信息 + // 可以写成: + // let t = array[i]; array[i] = array[j]; array[j] = t + [array[i], array[j]] = [array[j], array[i]]; } } ``` -让我们以相同的方式测试它: +让我们以相同的方式测试一下: ```js run function shuffle(array) { @@ -83,7 +89,7 @@ function shuffle(array) { } } -// 出现所有可能排列的次数 +// 所有可能排列的出现次数 let count = { '123': 0, '132': 0, @@ -99,7 +105,7 @@ for (let i = 0; i < 1000000; i++) { count[array.join('')]++; } -// 出现所有可能排列的次数 +// 显示所有可能排列的出现次数 for (let key in count) { alert(`${key}: ${count[key]}`); } @@ -118,4 +124,4 @@ for (let key in count) { 现在看起来不错:所有排列都以相同的概率出现。 -另外,性能方面 Fisher — Yates 算法要好得多,没有排序开销。 +另外,在性能方面,Fisher — Yates 算法要好得多,没有“排序”开销。 diff --git a/1-js/05-data-types/05-array-methods/9-shuffle/task.md b/1-js/05-data-types/05-array-methods/9-shuffle/task.md index bbf97f5293..a169c3ea5e 100644 --- a/1-js/05-data-types/05-array-methods/9-shuffle/task.md +++ b/1-js/05-data-types/05-array-methods/9-shuffle/task.md @@ -2,11 +2,11 @@ importance: 3 --- -# 随机排序 +# 随机排列数组 -编写函数 `shuffle(array)` 混洗(随机重新排序)数组的元素。 +编写函数 `shuffle(array)` 来随机排列数组的元素。 -多次运行 `shuffle` 可以导致不同的元素顺序。例如: +多次运行 `shuffle` 可能导致元素顺序的不同。例如: ```js let arr = [1, 2, 3]; @@ -22,5 +22,4 @@ shuffle(arr); // ... ``` -所有元素顺序应该具有相等的概率。例如,`[1,2,3]` 可以重新排序为 `[1,2,3]` 或 `[1,3,2]` 或 `[3,1,2]` 等。每种情况的概率相等。 - +所有元素顺序应该具有相等的概率。例如,可以将 `[1,2,3]` 重新排序为 `[1,2,3]` 或 `[1,3,2]` 或 `[3,1,2]` 等,每种情况的概率相等。 diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index f7a7850623..6a4b389360 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,15 +1,15 @@ # 数组方法 -因为数组提供的方法很多。为了方便起见,在本章中,我们将按组讲解。 +数组提供的方法有很多。为了方便起见,在本章中,我们将按组讲解。 ## 添加/移除数组元素 -已知从开头或结尾添加删除元素的方法: +我们已经学了从数组的首端或尾端添加和删除元素的方法: -- `arr.push(...items)` — 从结尾添加元素, -- `arr.pop()` — 从结尾提取元素, -- `arr.shift()` — 从开头提取元素, -- `arr.unshift(...items)` — 从开头添加元素, +- `arr.push(...items)` — 从尾端添加元素, +- `arr.pop()` — 从尾端提取元素, +- `arr.shift()` — 从首端提取元素, +- `arr.unshift(...items)` — 从首端添加元素。 这里还有其他几种方法。 @@ -30,13 +30,13 @@ alert( arr[1] ); // undefined alert( arr.length ); // 3 ``` -元素被删除,但数组仍然有 3 个元素,我们可以看到 `arr.length == 3`。 +元素被删除了,但数组仍然有 3 个元素,我们可以看到 `arr.length == 3`。 -这很正常,因为 `delete obj.key` 是通过 `key` 来移除对应的值。但是对于数组,我们通常希望剩下的元素移除并释放占用的位置,得到一个更短的数组。 +这很正常,因为 `delete obj.key` 是通过 `key` 来移除对应的值。对于对象来说是可以的。但是对于数组来说,我们通常希望剩下的元素能够移动并占据被释放的位置。我们希望得到一个更短的数组。 所以应该使用特殊的方法。 -[arr.splice(str)](mdn:js/Array/splice) 方法可以说是数组界的瑞士军刀。它可以做所有事情:添加,删除和插入元素。 +[arr.splice(str)](mdn:js/Array/splice) 方法可以说是处理数组的瑞士军刀。它可以做所有事情:添加,删除和插入元素。 语法是: @@ -44,9 +44,9 @@ alert( arr.length ); // 3 arr.splice(index[, deleteCount, elem1, ..., elemN]) ``` -从 `index` 开始:删除 `deleteCount` 元素并在当前位置插入 `elem1, ..., elemN`。最后返回已删除元素的数组。 +从 `index` 开始:删除 `deleteCount` 个元素并在当前位置插入 `elem1, ..., elemN`。最后返回已删除元素的数组。 -这个方法很容易通过例子来掌握。 +通过例子我们可以很容易地掌握这个方法。 让我们从删除开始: @@ -54,7 +54,7 @@ arr.splice(index[, deleteCount, elem1, ..., elemN]) let arr = ["I", "study", "JavaScript"]; *!* -arr.splice(1, 1); // from index 1 remove 1 element +arr.splice(1, 1); // 从索引 1 开始删除 1 个元素 */!* alert( arr ); // ["I", "JavaScript"] @@ -73,39 +73,39 @@ arr.splice(0, 3, "Let's", "dance"); alert( arr ) // now [*!*"Let's", "dance"*/!*, "right", "now"] ``` -在这里我们可以看到 `splice` 返回已删除元素的数组: +在这里我们可以看到 `splice` 返回了已删除元素的数组: ```js run let arr = [*!*"I", "study",*/!* "JavaScript", "right", "now"]; -// remove 2 first elements +// 删除前两个元素 let removed = arr.splice(0, 2); -alert( removed ); // "I", "study" <-- 被删除元素的数组 +alert( removed ); // "I", "study" <-- 被从数组中删除了的元素 ``` -我们可以将 `deleteCount` 设置为 `0`,`splice` 方法就能够插入元素而不用删除: +我们可以将 `deleteCount` 设置为 `0`,`splice` 方法就能够插入元素而不用删除任何元素: ```js run let arr = ["I", "study", "JavaScript"]; -// from index 2 -// delete 0 -// then insert "complex" and "language" +// 从索引 2 开始 +// 删除 0 个元素 +// 然后插入 "complex" 和 "language" arr.splice(2, 0, "complex", "language"); alert( arr ); // "I", "study", "complex", "language", "JavaScript" ``` ````smart header="允许负向索引" -在这里和其他数组方法中,负向索引是允许的。它们从数组末尾计算位置,如下所示: +在这里和其他数组方法中,负向索引都是被允许的。它们从数组末尾计算位置,如下所示: ```js run let arr = [1, 2, 5]; -// from index -1 (one step from the end) -// delete 0 elements, -// then insert 3 and 4 +// 从索引 -1(尾端前一位) +// 删除 0 个元素, +// 然后插入 3 和 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5 @@ -119,29 +119,28 @@ alert( arr ); // 1,2,3,4,5 语法是: ```js -arr.slice(start, end) +arr.slice([start], [end]) ``` -它从所有元素的开始索引 `"start"` 复制到 `"end"` (不包括 `"end"`) 返回一个新的数组。`start` 和 `end` 都可以是负数,在这种情况下,从末尾计算索引。 +它会返回一个新数组,将所有从索引 `start` 到 `end`(不包括 `end`)的数组项复制到一个新的数组。`start` 和 `end` 都可以是负数,在这种情况下,从末尾计算索引。 -它和字符串的 `str.slice` 有点像,就是把子字符串替换成子数组。 +它和字符串的 `str.slice` 方法有点像,就是把子字符串替换成子数组。 例如: ```js run -let str = "test"; let arr = ["t", "e", "s", "t"]; -alert( str.slice(1, 3) ); // es -alert( arr.slice(1, 3) ); // e,s +alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素) -alert( str.slice(-2) ); // st -alert( arr.slice(-2) ); // s,t +alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素) ``` +我们也可以不带参数地调用它:`arr.slice()` 会创建一个 `arr` 的副本。其通常用于获取副本,以进行不影响原始数组的进一步转换。 + ### concat -[arr.concat](mdn:js/Array/concat) 将数组与其他数组和/或元素结合在一起。 +[arr.concat](mdn:js/Array/concat) 创建一个新数组,其中包含来自于其他数组和其他项的值。 语法: @@ -149,28 +148,28 @@ alert( arr.slice(-2) ); // s,t arr.concat(arg1, arg2...) ``` -它接受任意数量的参数 — 数组或值。 +它接受任意数量的参数 — 数组或值都可以。 -结果是一个包含`arr`,`arg1`,`arg2`等元素的新数组。 +结果是一个包含来自于 `arr`,然后是 `arg1`,`arg2` 的元素的新数组。 -如果参数是一个数组或具有 `Symbol.isConcatSpreadable` 属性,则其所有元素都将被复制。否则,复制参数本身。 +如果参数 `argN` 是一个数组,那么其中的所有元素都会被复制。否则,将复制参数本身。 例如: ```js run let arr = [1, 2]; -// merge arr with [3,4] -alert( arr.concat([3, 4])); // 1,2,3,4 +// create an array from: arr and [3,4] +alert( arr.concat([3, 4]) ); // 1,2,3,4 -// merge arr with [3,4] and [5,6] -alert( arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6 +// create an array from: arr and [3,4] and [5,6] +alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 -// merge arr with [3,4], then add values 5 and 6 -alert( arr.concat([3, 4], 5, 6)); // 1,2,3,4,5,6 +// create an array from: arr and [3,4], then add values 5 and 6 +alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` -通常,它只复制数组中的元素(“扩展”它们)。其他对象,即使它们看起来像数组一样,仍然作为一个整体添加: +通常,它只复制数组中的元素。其他对象,即使它们看起来像数组一样,但仍然会被作为一个整体添加: ```js run let arr = [1, 2]; @@ -181,10 +180,9 @@ let arrayLike = { }; alert( arr.concat(arrayLike) ); // 1,2,[object Object] -//[1, 2, arrayLike] ``` -...但是,如果类似数组的对象具有 `Symbol.isConcatSpreadable` 属性,将替换其元素: +……但是,如果类似数组的对象具有 `Symbol.isConcatSpreadable` 属性,那么它就会被 `concat` 当作一个数组来处理:此对象中的元素将被添加: ```js run let arr = [1, 2]; @@ -201,17 +199,46 @@ let arrayLike = { alert( arr.concat(arrayLike) ); // 1,2,something,else ``` -## 查询数组 +## 遍历:forEach + +[arr.forEach](mdn:js/Array/forEach) 方法允许为数组的每个元素都运行一个函数。 + +语法: +```js +arr.forEach(function(item, index, array) { + // ... do something with item +}); +``` -这些是在数组中查询某些内容的方法。 +例如,下面这个程序显示了数组的每个元素: + +```js run +// 对每个元素调用 alert +["Bilbo", "Gandalf", "Nazgul"].forEach(alert); +``` + +而这段代码更详细地介绍了它们在目标数组中的位置: + +```js run +["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { + alert(`${item} is at index ${index} in ${array}`); +}); +``` + +该函数的结果(如果它有返回)会被抛弃和忽略。 + + +## 在数组中搜索 + +现在,让我们介绍在数组中进行搜索的方法。 ### indexOf/lastIndexOf 和 includes -[arr.indexOf](mdn:js/Array/indexOf)、[arr.lastIndexOf](mdn:js/Array/lastIndexOf) 和 [arr.includes](mdn:js/Array/includes) 方法与字符串操作具有相同的语法,只不过这里是对数组元素而不是字符进行操作: +[arr.indexOf](mdn:js/Array/indexOf)、[arr.lastIndexOf](mdn:js/Array/lastIndexOf) 和 [arr.includes](mdn:js/Array/includes) 方法与字符串操作具有相同的语法,并且作用基本上也与字符串的方法相同,只不过这里是对数组元素而不是字符进行操作: -- `arr.indexOf(item, from)` 从索引 `from` 查询 `item`,如果找到返回索引,否则返回 `-1`。 -- `arr.lastIndexOf(item, from)` — 和上面相同,只是从尾部开始查询。 -- `arr.includes(item, from)` — 从索引 `from` 查询 `item`,如果找到则返回 `true`。 +- `arr.indexOf(item, from)` 从索引 `from` 开始搜索 `item`,如果找到则返回索引,否则返回 `-1`。 +- `arr.lastIndexOf(item, from)` — 和上面相同,只是从右向左搜索。 +- `arr.includes(item, from)` — 从索引 `from` 开始搜索 `item`,如果找到则返回 `true`(译注:如果没找到,则返回 `false`)。 例如: @@ -225,16 +252,16 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -请注意,这些方法使用 `===` 比较。所以如果我们查询 `false`,会精确到是 `false` 而不是零。 +请注意,这些方法使用的是严格相等 `===` 比较。所以如果我们搜索 `false`,会精确到的确是 `false` 而不是数字 `0`。 -如果我们想检查是否包含需要的元素,并且不想知道确切的索引,那么 `arr.includes` 是首选。 +如果我们想检查是否包含某个元素,并且不想知道确切的索引,那么 `arr.includes` 是首选。 此外,`includes` 的一个非常小的差别是它能正确处理`NaN`,而不像 `indexOf/lastIndexOf`: ```js run const arr = [NaN]; -alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN) -alert( arr.includes(NaN) );// true (correct) +alert( arr.indexOf(NaN) ); // -1(应该为 0,但是严格相等 === equality 对 NaN 无效) +alert( arr.includes(NaN) );// true(这个结果是对的) ``` ### find 和 findIndex @@ -243,22 +270,23 @@ alert( arr.includes(NaN) );// true (correct) 这时可以用 [arr.find](mdn:js/Array/find) 方法。 -语法: +语法如下: ```js let result = arr.find(function(item, index, array) { -  // 如果查询到返回 true + // 如果返回 true,则返回 item 并停止迭代 + // 对于 falsy 则返回 undefined }); ``` -该函数对数组中的每个元素重复调用: +依次对数组中的每个元素调用该函数: - `item` 是元素。 - `index` 是它的索引。 - `array` 是数组本身。 -如果它返回`true`,则查询停止,返回 `item`。如果没有查询到,则返回 `undefined`。 +如果它返回 `true`,则搜索停止,并返回 `item`。如果没有搜索到,则返回 `undefined`。 -例如,我们有一组用户,每个用户都有 `id` 和 `name` 字段。让我们找到一个 `id == 1`: +例如,我们有一个存储用户的数组,每个用户都有 `id` 和 `name` 字段。让我们找到 `id == 1` 的那个用户: ```js run let users = [ @@ -272,23 +300,24 @@ let user = users.find(item => item.id == 1); alert(user.name); // John ``` -在现实生活中,对象数组是很常见,所以`find` 方法非常有用。 +在现实生活中,对象数组是很常见的,所以 `find` 方法非常有用。 -注意在这个例子中我们传给了 `find` 一个单参数函数 `item => item.id == 1`。其他参数 `find` 很少使用。 +注意在这个例子中,我们传给了 `find` 一个单参数函数 `item => item.id == 1`。这很典型,并且 `find` 方法的其他参数很少使用。 -与 [arr.findIndex](mdn:js/Array/findIndex) 方法本质上是相同的,但它返回找到元素的索引而不是元素本身。 +[arr.findIndex](mdn:js/Array/findIndex) 方法(与 `arr.find` 方法)基本上是一样的,但它返回找到元素的索引,而不是元素本身。并且在未找到任何内容时返回 `-1`。 ### filter -`find` 方法查询的是使函数返回 `true` 的第一个元素。 +`find` 方法搜索的是使函数返回 `true` 的第一个(单个)元素。 如果需要匹配的有很多,我们可以使用 [arr.filter(fn)](mdn:js/Array/filter)。 -语法与 `find` 大致相同,但是它返回的是所有匹配元素组成的数组: +语法与 `find` 大致相同,但是 `filter` 返回的是所有匹配元素组成的数组: ```js let results = arr.filter(function(item, index, array) { -  // 在元素通过过滤器时返回 true + // 如果 true item 被 push 到 results,迭代继续 + // 如果什么都没找到,则返回空数组 }); ``` @@ -309,13 +338,14 @@ alert(someUsers.length); // 2 ## 转换数组 -本节介绍转换或重新排序数组的方法。 - +让我们继续学习进行数组转换和重新排序的方法。 ### map [arr.map](mdn:js/Array/map) 方法是最有用和经常使用的方法之一。 +它对数组的每个元素都调用函数,并返回结果数组。 + 语法: ```js @@ -324,25 +354,25 @@ let result = arr.map(function(item, index, array) { }) ``` -它对数组中每个元素调用函数并返回符合结果的数组。 - 例如,在这里我们将每个元素转换为它的字符串长度: ```js run -let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length) +let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); alert(lengths); // 5,7,6 ``` ### sort(fn) -[arr.sort](mdn:js/Array/sort) 方法对数组**进行排序** +[arr.sort](mdn:js/Array/sort) 方法对数组进行 **原位(in-place)** 排序,更改元素的顺序。(译注:原位是指在此数组内,而非生成一个新数组。) + +它还返回排序后的数组,但是返回值通常会被忽略,因为修改了 `arr` 本身。 语法: ```js run let arr = [ 1, 2, 15 ]; -// 该方法重新排列 arr 的内容(并返回它) +// 该方法重新排列 arr 的内容 arr.sort(); alert( arr ); // *!*1, 15, 2*/!* @@ -352,22 +382,22 @@ alert( arr ); // *!*1, 15, 2*/!* 顺序变成了 `1, 15, 2`。不对,但为什么呢? -**这些元素默认情况下按字符串排序。** +**这些元素默认情况下被按字符串进行排序。** -从字面上看,所有元素都被转换为字符串,然后进行比较。因此,按照词典顺序排序,实际上应该是`"2" > "15"`。 +从字面上看,所有元素都被转换为字符串,然后进行比较。对于字符串,按照词典顺序进行排序,实际上应该是 `"2" > "15"`。 -要使用我们自己的排序顺序,我们需要提供带两个参数的函数作为 `arr.sort()` 的参数。 +要使用我们自己的排序顺序,我们需要提供一个函数作为 `arr.sort()` 的参数。 -该函数像这样工作: +该函数应该比较两个任意值并返回: ```js function compare(a, b) { - if (a > b) return 1; - if (a == b) return 0; - if (a < b) return -1; + if (a > b) return 1; // 如果第一个值比第二个值大 + if (a == b) return 0; // 如果两个值相等 + if (a < b) return -1; // 如果第一个值比第二个值小 } ``` -例如: +例如,按数字进行排序: ```js run function compareNumeric(a, b) { @@ -387,11 +417,11 @@ alert(arr); // *!*1, 2, 15*/!* 现在结果符合预期了。 -让我们搁置一边,思考发生了什么。`arr` 可以是由任何东西组成的数组。它可能包含数字或字符串或 html 元素或其他。我们对一组数据进行排序时,需要一个**排序函数**来确认如何比较这些元素。默认是按字符串排序的。 +我们思考一下这儿发生了什么。`arr` 可以是由任何内容组成的数组,对吗?它可能包含数字、字符串、对象或其他任何内容。我们有一组 **一些元素**。要对其进行排序,我们需要一个 **排序函数** 来确认如何比较这些元素。默认是按字符串进行排序的。 -`arr.sort(fn)` 方法内置实现排序算法。我们不需要关心它是如何工作的(大多数情况下是优化过的[快速排序](https://en.wikipedia.org/wiki/Quicksort)算法)。它将自动遍历数组,使用提供的函数比较它的元素并对它们重新排序,我们所需要的只是提供用于比较的函数 `fn`。 +`arr.sort(fn)` 方法实现了通用的排序算法。我们不需要关心它的内部工作原理(大多数情况下都是经过 [快速排序](https://en.wikipedia.org/wiki/Quicksort) 算法优化的)。它将遍历数组,使用提供的函数比较其元素并对其重新排序,我们所需要的就是提供执行比较的函数 `fn`。 -顺便说一句,如果我们想知道哪些元素进行了比较 — alert 是没有作用的: +顺便说一句,如果我们想知道要比较哪些元素 — 那么什么都不会阻止 alert 它们: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -399,11 +429,10 @@ alert(arr); // *!*1, 2, 15*/!* }); ``` -该算法可以在过程中多次比较元素,但它会尽可能少地进行比较。 - +该算法可以在此过程中,将一个元素与多个其他元素进行比较,但是它会尝试进行尽可能少的比较。 ````smart header="比较函数可以返回任何数字" -实际上,比较函数只需要返回一个正数表示更大,而负数表示更少。 +实际上,比较函数只需要返回一个正数表示“大于”,一个负数表示“小于”。 通过这个原理我们可以编写更短的函数: @@ -417,18 +446,34 @@ alert(arr); // *!*1, 2, 15*/!* ```` ````smart header="箭头函数最好" -[箭头函数](info:arrow-functions-basics) 还记得吗?这里使用箭头函数会更加简洁: +你还记得 [箭头函数](info:arrow-functions-basics) 吗?这里使用箭头函数会更加简洁: ```js arr.sort( (a, b) => a - b ); ``` -这与上面的那些更长的其它写法是完全相同的。 +这与上面更长的版本完全相同。 +```` + +````smart header="使用 `localeCompare` for strings" +你记得 [字符串比较](info:string#correct-comparisons) 算法吗?默认情况下,它通过字母的代码比较字母。 + +对于许多字母,最好使用 `str.localeCompare` 方法正确地对字母进行排序,例如 `Ö`。 + +例如,让我们用德语对几个国家/地区进行排序: + +```js run +let countries = ['Österreich', 'Andorra', 'Vietnam']; + +alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich(错的) + +alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam(对的!) +``` ```` ### reverse -[arr.reverse](mdn:js/Array/reverse) 方法颠倒 `arr` 中元素的顺序。 +[arr.reverse](mdn:js/Array/reverse) 方法用于颠倒 `arr` 中元素的顺序。 例如: @@ -439,15 +484,15 @@ arr.reverse(); alert( arr ); // 5,4,3,2,1 ``` -它也在返回后返回数组 `arr`。 +它也会返回颠倒后的数组 `arr`。 ### split 和 join -举一个现实生活的场景的例子,我们正在编写一个消息应用程序,并且该人员输入以逗号分隔的接收者列表:`John,Pete,Mary`。但对我们来说,数组比单个字符串更舒适。怎么做才能获得这个数组呢? +举一个现实生活场景的例子。我们正在编写一个消息应用程序,并且该人员输入以逗号分隔的接收者列表:`John, Pete, Mary`。但对我们来说,名字数组比单个字符串舒适得多。怎么做才能获得这样的数组呢? [str.split(delim)](mdn:js/String/split) 方法可以做到。它通过给定的分隔符 `delim` 将字符串分割成一个数组。 -在下面的例子中,我们用逗号分隔空格: +在下面的例子中,我们用“逗号后跟着一个空格”作为分隔符: ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -455,11 +500,11 @@ let names = 'Bilbo, Gandalf, Nazgul'; let arr = names.split(', '); for (let name of arr) { - alert( `A message to ${name}.` ); // A message to Bilbo (and other names) + alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字) } ``` -`split` 方法有一个可选的第二个数字参数 — 对数组长度的限制。如果提供了,那么额外的元素将被忽略。但实际上它很少使用: +`split` 方法有一个可选的第二个数字参数 — 对数组长度的限制。如果提供了,那么额外的元素会被忽略。但实际上它很少使用: ```js run let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); @@ -468,7 +513,7 @@ alert(arr); // Bilbo, Gandalf ``` ````smart header="拆分为字母" -调用空的参数 `split(s)` 会将字符串分成一个字母数组: +调用带有空参数 `s` 的 `split(s)`,会将字符串拆分为字母数组: ```js run let str = "test"; @@ -477,47 +522,52 @@ alert( str.split('') ); // t,e,s,t ``` ```` -[arr.join(str)](mdn:js/Array/join) 与 `split` 相反。它会在它们之间创建一串由 `str` 粘合的 `arr` 项。 +[arr.join(glue)](mdn:js/Array/join) 与 `split` 相反。它会在它们之间创建一串由 `glue` 粘合的 `arr` 项。 例如: ```js run let arr = ['Bilbo', 'Gandalf', 'Nazgul']; -let str = arr.join(';'); +let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串 alert( str ); // Bilbo;Gandalf;Nazgul ``` ### reduce/reduceRight -当我们需要遍历一个数组时 — 我们可以使用 `forEach`。 +当我们需要遍历一个数组时 — 我们可以使用 `forEach`,`for` 或 `for..of`。 -当我们需要迭代并返回每个元素的数据时 — 我们可以使用 `map`。 +当我们需要遍历并返回每个元素的数据时 — 我们可以使用 `map`。 -[arr.reduce](mdn:js/Array/reduce) 和 [arr.reduceRight](mdn:js/Array/reduceRight) 和上面差不多,但有点复杂。它们用于根据数组计算单个值。 +[arr.reduce](mdn:js/Array/reduce) 方法和 [arr.reduceRight](mdn:js/Array/reduceRight) 方法和上面的种类差不多,但稍微复杂一点。它们用于根据数组计算单个值。 语法是: ```js -let value = arr.reduce(function(previousValue, item, index, arr) { +let value = arr.reduce(function(accumulator, item, index, array) { // ... -}, initial); +}, [initial]); ``` -该函数应用于元素。从第二个参数开始你可能就会觉得很眼熟了: +该函数一个接一个地应用于所有数组元素,并将其结果“搬运(carry on)”到下一个调用。 +参数: + +- `accumulator` - 是上一个函数调用的结果,第一次等于 `initial`(如果提供了 `initial` 的话)。 - `item` — 当前的数组元素。 - `index` — 当前索引。 - `arr` — 数组本身。 -目前为止,这很像 `forEach/map`。但还有一个参数不同就是: +应用函数时,上一个函数调用的结果将作为第一个参数传递给下一个函数。 + +因此,第一个参数本质上是累加器,用于存储所有先前执行的组合结果。最后,它成为 `reduce` 的结果。 -- `previousValue` — 是前一个函数调用的结果,第一次调用是初始化。 +听起来复杂吗? -我们写个例子试试。 +掌握这个知识点的最简单的方法就是通过示例。 -这里我们得到一行数组的总和: +在这里,我们通过一行代码得到一个数组的总和: ```js run let arr = [1, 2, 3, 4, 5]; @@ -527,45 +577,44 @@ let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 ``` -在这里,我们使用了 `reduce` 的最常见类型,它只使用 2 个参数。 +传递给 `reduce` 的函数仅使用了 2 个参数,通常这就足够了。 -让我们看看发生了什么的细节。 +让我们看看细节,到底发生了什么。 -1. 在第一次运行时,`sum` 是初始值(`reduce` 的最后一个参数),等于 0,`current` 是第一个数组元素,等于 1。所以结果是 `1`。 -2. 在第二次运行时,`sum = 1`,我们添加第二个数组元素(`2`)并返回。 -3. 在第三次运行中,`sum = 3`,我们再添加一个元素,等等...... +1. 在第一次运行时,`sum` 的值为初始值 `initial`(`reduce` 的最后一个参数),等于 0,`current` 是第一个数组元素,等于 `1`。所以函数运行的结果是 `1`。 +2. 在第二次运行时,`sum = 1`,我们将第二个数组元素(`2`)与其相加并返回。 +3. 在第三次运行中,`sum = 3`,我们继续把下一个元素与其相加,以此类推…… 计算流程: ![](reduce.svg) -或者以表格的形式出现,每行代表的是下一个数组元素的函数调用: +或者以表格的形式表示,每一行代表的是对下一个数组元素的函数调用: | |`sum`|`current`|`result`| |---|-----|---------|---------| -|the first call|`0`|`1`|`1`| -|the second call|`1`|`2`|`3`| -|the third call|`3`|`3`|`6`| -|the fourth call|`6`|`4`|`10`| -|the fifth call|`10`|`5`|`15`| +|第 1 次调用|`0`|`1`|`1`| +|第 2 次调用|`1`|`2`|`3`| +|第 3 次调用|`3`|`3`|`6`| +|第 4 次调用|`6`|`4`|`10`| +|第 5 次调用|`10`|`5`|`15`| - -正如我们所看到的,先前调用的结果成为下一个调用的第一个参数。 +在这里,我们可以清楚地看到上一个调用的结果如何成为下一个调用的第一个参数。 我们也可以省略初始值: ```js run let arr = [1, 2, 3, 4, 5]; -// 删除初始值 +// 删除 reduce 的初始值(没有 0) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15 ``` -结果是一样的。这是因为如果没有初始值,那么 `reduce` 将数组的第一个元素作为初始值,并从第二个元素开始迭代。 +结果是一样的。这是因为如果没有初始值,那么 `reduce` 会将数组的第一个元素作为初始值,并从第二个元素开始迭代。 -计算表与上面相同,减去第一行 +计算表与上面相同,只是去掉第一行。 但是这种使用需要非常小心。如果数组为空,那么在没有初始值的情况下调用 `reduce` 会导致错误。 @@ -575,56 +624,27 @@ alert( result ); // 15 let arr = []; // Error: Reduce of empty array with no initial value -// 如果初始值存在,reduce 将返回空 arr。 +// 如果初始值存在,则 reduce 将为空 arr 返回它(即这个初始值)。 arr.reduce((sum, current) => sum + current); ``` - 所以建议始终指定初始值。 -[arr.reduceRight](mdn:js/Array/reduceRight) 也一样,但是遍历是从右到左。 - - -## 迭代:forEach - -[arr.forEach](mdn:js/Array/forEach) 方法允许为数组的每个元素运行一个函数。 - -语法: -```js -arr.forEach(function(item, index, array) { - // ... do something with item -}); -``` - -例如,这显示了数组的每个元素: - -```js run -// 为每个元素调用 alert -["Bilbo", "Gandalf", "Nazgul"].forEach(alert); -``` - -而这段代码更详细地介绍了它们在数组中的位置 +[arr.reduceRight](mdn:js/Array/reduceRight) 和 [arr.reduce](mdn:js/Array/reduce) 方法的功能一样,只是遍历为从右到左。 -```js run -["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { - alert(`${item} is at index ${index} in ${array}`); -}); -``` - -该函数的结果(如果它返回的话)被抛弃并被忽略。 ## Array.isArray -数组基于对象。不构成单独的语言类型。 +数组是基于对象的,不构成单独的语言类型。 -所以 `typeof` 无法从对象中区分出数组来: +所以 `typeof` 不能帮助从数组中区分出普通对象: ```js run alert(typeof {}); // object alert(typeof []); // same ``` -......但是数组经常被使用,以至于有一种特殊的方法用于判断:[Array.isArray(value)](mdn:js/Array/isArray)。如果 `value` 是一个数组,则返回 `true`;否则返回 `false`。 +……但是数组经常被使用,因此有一种特殊的方法用于判断:[Array.isArray(value)](mdn:js/Array/isArray)。如果 `value` 是一个数组,则返回 `true`;否则返回 `false`。 ```js run alert(Array.isArray({})); // false @@ -632,11 +652,11 @@ alert(Array.isArray({})); // false alert(Array.isArray([])); // true ``` -## 大多数方法支持 "thisArg" +## 大多数方法都支持 "thisArg" -几乎所有调用函数的数组方法 - 比如 `find`,`filter`,`map`,与带有 `sort` 的不同,他们接受一个可选的附加参数 `thisArg`。 +几乎所有调用函数的数组方法 - 比如 `find`,`filter`,`map`,除了 `sort` 是一个特例,都接受一个可选的附加参数 `thisArg`。 -该参数在上面的部分没有解释,因为它很少使用。但为了完整性,我们还需要解释下。 +上面的部分中没有解释该参数,因为该参数很少使用。但是为了完整性,我们需要讲讲它。 以下是这些方法的完整语法: @@ -650,80 +670,86 @@ arr.map(func, thisArg); `thisArg` 参数的值在 `func` 中变为 `this`。 -例如,在这里我们使用一个对象方法作为过滤器,`thisArg` 派上用场: +例如,在这里我们使用 `army` 对象方法作为过滤器,`thisArg` 用于传递上下文(passes the context): ```js run -let user = { - age: 18, - younger(otherUser) { - return otherUser.age < this.age; +let army = { + minAge: 18, + maxAge: 27, + canJoin(user) { + return user.age >= this.minAge && user.age < this.maxAge; } }; let users = [ - {age: 12}, {age: 16}, - {age: 32} + {age: 20}, + {age: 23}, + {age: 30} ]; *!* -// 找到比 user 小的所有 users -let youngerUsers = users.filter(user.younger, user); +// 找到 army.canJoin 返回 true 的 user +let soldiers = users.filter(army.canJoin, army); */!* -alert(youngerUsers.length); // 2 +alert(soldiers.length); // 2 +alert(soldiers[0].age); // 20 +alert(soldiers[1].age); // 23 ``` -在上面我们使用 `user.younger` 作为过滤器,并提供 `user` 作为它的上下文。如果我们没有提供上下文,`users.filter(user.younger)` 会调用`user.younger` 作为一个独立的函数,这时 `this=undefined`。 +如果在上面的示例中我们使用了 `users.filter(army.canJoin)`,那么 `army.canJoin` 将被作为独立函数调用,并且这时 `this=undefined`,从而会导致即时错误。 + +可以用 `users.filter(user => army.canJoin(user))` 替换对 `users.filter(army.canJoin, army)` 的调用。前者的使用频率更高,因为对于大多数人来说,它更容易理解。 ## 总结 -数组方法备忘录: +数组方法备忘单: - 添加/删除元素: - - `push(...items)` — 从结尾添加元素, - - `pop()` — 从结尾提取元素, - - `shift()` — 从开头提取元素, - - `unshift(...items)` — 从开头添加元素, - - `splice(pos, deleteCount, ...items)` — 从 `index` 开始:删除 `deleteCount` 元素并在当前位置插入元素。 - - `slice(start, end)` — 它从所有元素的开始索引 `"start"` 复制到 `"end"` (不包括 `"end"`) 返回一个新的数组。 - - `concat(...items)` — 返回一个新数组:复制当前数组的所有成员并向其中添加 `items`。如果有任何` items` 是一个数组,那么就取其元素。 - -- 查询元素: - - `indexOf/lastIndexOf(item, pos)` — 从 `pos` 找到 `item`,则返回索引否则返回 `-1`。 + - `push(...items)` — 向尾端添加元素, + - `pop()` — 从尾端提取一个元素, + - `shift()` — 从首端提取一个元素, + - `unshift(...items)` — 向首端添加元素, + - `splice(pos, deleteCount, ...items)` — 从 `index` 开始删除 `deleteCount` 个元素,并在当前位置插入 `items`。 + - `slice(start, end)` — 创建一个新数组,将从位置 `start` 到位置 `end`(但不包括 `end`)的元素复制进去。 + - `concat(...items)` — 返回一个新数组:复制当前数组的所有元素,并向其中添加 `items`。如果 `items` 中的任意一项是一个数组,那么就取其元素。 + +- 搜索元素: + - `indexOf/lastIndexOf(item, pos)` — 从位置 `pos` 开始搜索 `item`,搜索到则返回该项的索引,否则返回 `-1`。 - `includes(value)` — 如果数组有 `value`,则返回 `true`,否则返回 `false`。 - - `find/filter(func)` — 通过函数过滤元素,返回符合 `true` 条件的第一个值/所有值。 + - `find/filter(func)` — 通过 `func` 过滤元素,返回使 `func` 返回 `true` 的第一个值/所有值。 - `findIndex` 和 `find` 类似,但返回索引而不是值。 +- 遍历元素: + - `forEach(func)` — 对每个元素都调用 `func`,不返回任何内容。 + - 转换数组: - - `map(func)` — 从每个元素调用 `func` 的结果创建一个新数组。 - - `sort(func)` — 将数组倒序排列,然后返回。 - - `reverse()` — 在原地颠倒数组,然后返回它。 + - `map(func)` — 根据对每个元素调用 `func` 的结果创建一个新数组。 + - `sort(func)` — 对数组进行原位(in-place)排序,然后返回它。 + - `reverse()` — 原位(in-place)反转数组,然后返回它。 - `split/join` — 将字符串转换为数组并返回。 - - `reduce(func, initial)` — 通过为每个元素调用 `func` 计算数组上的单个值并在调用之间传递中间结果。 - -- 迭代元素: - - `forEach(func)` — 为每个元素调用 `func`,不返回任何东西。 + - `reduce(func, initial)` — 通过对每个元素调用 `func` 计算数组上的单个值,并在调用之间传递中间结果。 - 其他:  - `Array.isArray(arr)` 检查 `arr` 是否是一个数组。 -请注意,`sort`,`reverse` 和 `splice` 方法修改数组本身。 +请注意,`sort`,`reverse` 和 `splice` 方法修改的是数组本身。 -这些方法是最常用的方法,它们覆盖 99% 的用例。但是还有其他几个: +这些是最常用的方法,它们覆盖 99% 的用例。但是还有其他几个: - [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) 检查数组。 - 在类似于 `map` 的数组的每个元素上调用函数 `fn`。如果任何/所有结果为 `true`,则返回 `true`,否则返回 `false`。 + 与 `map` 类似,对数组的每个元素调用函数 `fn`。如果任何/所有结果为 `true`,则返回 `true`,否则返回 `false`。 -- [arr.fill(value, start, end)](mdn:js/Array/fill) — 从 `start` 到 `end` 用 `value` 重复填充数组。 +- [arr.fill(value, start, end)](mdn:js/Array/fill) — 从索引 `start` 到 `end`,用重复的 `value` 填充数组。 -- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) — 将从 `start`(开始)位置到 `end`(结束)位置 [译注:不包含结束位元素] 的所有元素复制到 *自身* 的 `target`(目标)位置中(覆盖现有元素)。 +- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) — 将从位置 `start` 到 `end` 的所有元素复制到 **自身** 的 `target` 位置(覆盖现有元素)。 -有关完整列表,请参阅[手册](mdn:js/Array)。 +有关完整列表,请参阅 [手册](mdn:js/Array)。 -从第一眼看来,似乎有很多方法,很难记住。但实际上这比看起来要容易得多。 +乍看起来,似乎有很多方法,很难记住。但实际上这比看起来要容易得多。 -为了您有数组方法的经验,请仔细查看备忘单了解它们。然后解决本章的任务练习。 +浏览这个备忘单,以了解这些方法。然后解决本章中的习题来进行练习,以便让你有数组方法的使用经验。 -以后,当你需要对某个数组操作,而无从下手 — 可以来到这,查看备忘录并找到正确的方法。示例将帮助您正确编写它。很快你会自动记住这些方法,而无需你额外的努力。 +然后,每当你需要对数组进行某些操作,而又不知道怎么做的时候,请回到这儿,查看这个备忘单,然后找到正确的方法。示例将帮助你正确编写它。用不了多久,你就自然而然地记住这些方法了,根本不需要你死记硬背。