diff --git a/1-js/05-data-types/07-map-set/01-array-unique-map/task.md b/1-js/05-data-types/07-map-set/01-array-unique-map/task.md index 74778e4d21..3bd43dd5d0 100644 --- a/1-js/05-data-types/07-map-set/01-array-unique-map/task.md +++ b/1-js/05-data-types/07-map-set/01-array-unique-map/task.md @@ -2,17 +2,17 @@ importance: 5 --- -# 过滤唯一数组元素 +# 过滤数组中的唯一元素 定义 `arr` 为一个数组。 -创建一个函数 `unique(arr)`,函数将返回一个数组,包含 `arr` 中所有元素且元素均唯一。 +创建一个函数 `unique(arr)`,该函数返回一个由 `arr` 中所有唯一元素所组成的数组。 例如: ```js function unique(arr) { -  /* 你的代码 */ + /* 你的代码 */ } let values = ["Hare", "Krishna", "Hare", "Krishna", @@ -22,6 +22,6 @@ let values = ["Hare", "Krishna", "Hare", "Krishna", alert( unique(values) ); // Hare, Krishna, :-O ``` -附:这里用到了 string 类型,但其实可以是任何类型的值。 +P.S. 这里用到了 string 类型,但其实可以是任何类型的值。 -附:使用 `Set` 来存储唯一的数值。 +P.S. 使用 `Set` 来存储唯一值。 diff --git a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md index 984c44ad89..ee92034eca 100644 --- a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md @@ -1,4 +1,4 @@ -为了找到所有 anagrams,让我们把每个单词打散为字母并排序。当单词有序后,所有的 anagrams 都一样。 +为了找到所有字谜(anagram),让我们把每个单词打散为字母并进行排序。当字母被排序后,所有的字谜就都一样了。 例如: @@ -9,14 +9,14 @@ cheaters, hectares, teachers -> aceehrst ... ``` -我们将会使用排序后字母的变量作为 map 的键,为每一个键仅保存一个值: +我们将使用进行字母排序后的单词的变体(variant)作为 map 的键,每个键仅对应存储一个值: ```js run function aclean(arr) { let map = new Map(); for (let word of arr) { -    // 将单词打散为字母,排序然后拼接回来 + // 将单词 split 成字母,对字母进行排序,之后再 join 回来 *!* let sorted = word.toLowerCase().split('').sort().join(''); // (*) */!* @@ -31,7 +31,7 @@ let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; alert( aclean(arr) ); ``` -字母排序在行 `(*)` 以链式调用的方式完成。 +字母排序在 `(*)` 行以链式调用的方式完成。 为了方便,我们把它分解为多行: @@ -43,23 +43,23 @@ let sorted = word // PAN .join(''); // anp ``` -两个不同的单词 `'PAN'` 和 `'nap'` 得到了同样的字母排序格式 `'anp'`。 +两个不同的单词 `'PAN'` 和 `'nap'` 得到了同样的字母排序形式 `'anp'`。 -下一行将单词放入 map: +下一行是将单词放入 map: ```js map.set(sorted, word); ``` -如果我们遇到了一个有相同字母排序格式的单词,那么他就会覆盖 map 中相同键的前一个值。所以我们将总会得到每个字母格式下最靠后的单词。 +如果我们再次遇到相同字母排序形式的单词,那么它将会覆盖 map 中有相同键的前一个值。因此,每个字母形式(译注:排序后的)最多只有一个单词。(译注:并且是每个字母形式中最靠后的那个值) -最后 `Array.from(map.values())` 将 map 的值迭代(我们不需要结果的键)然后返回数组集合。 +最后,`Array.from(map.values())` 将 map 的值迭代(我们不需要结果的键)为数组形式,并返回这个数组。 -这里我们也可以使用一般的对象而不用 `Map`,因为键就是字符串。 +在这里,我们也可以使用普通对象(plain object)而不用 `Map`,因为键就是字符串。 -这就是答案: +下面是解决方案: -```js run +```js run demo function aclean(arr) { let obj = {}; @@ -68,7 +68,7 @@ function aclean(arr) { obj[sorted] = arr[i]; } - return Array.from(Object.values(obj)); + return Object.values(obj); } let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; diff --git a/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md index 603abaf6a1..d04fdfe589 100644 --- a/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/task.md @@ -2,9 +2,9 @@ importance: 4 --- -# 过滤 anagrams +# 过滤字谜(anagrams) -[Anagrams](https://en.wikipedia.org/wiki/Anagram) 是有相同数量相同字母但是顺序不同的单词。 +[Anagrams](https://en.wikipedia.org/wiki/Anagram) 是具有相同数量相同字母但是顺序不同的单词。 例如: @@ -14,7 +14,7 @@ ear - are - era cheaters - hectares - teachers ``` -写一个函数 `aclean(arr)`,它返回的数组中 anagrams 被删除。 +写一个函数 `aclean(arr)`,它返回被清除了字谜(anagrams)的数组。 例如: @@ -24,5 +24,5 @@ let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era" ``` -对于所有的 anagram 组,需要保留任意一个单词。 +对于所有的字谜(anagram)组,都应该保留其中一个词,但保留的具体是哪一个并不重要。 diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md b/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md index ba4053b249..3b57f52de1 100644 --- a/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/solution.md @@ -1,7 +1,7 @@ -这是因为 `map.keys()` 返回的是迭代器而非数组。 +这是因为 `map.keys()` 返回的是可迭代对象而非数组。 -我们可以使用方法 `Array.from` 来将它转化为数组: +我们可以使用方法 `Array.from` 来将它转换为数组: ```js run diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md index c86540e3d8..98d02a6503 100644 --- a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md @@ -4,9 +4,9 @@ importance: 5 # 迭代键 -我们希望得到 `map.keys()` 的数组然后继续对它进行处理(脱离开 map 本身)。 +我们期望使用 `map.keys()` 得到一个数组,然后使用特定的方法例如 `.push` 等,对其进行处理。 -但是有这样一个问题: +但是运行不了: ```js run let map = new Map(); @@ -16,9 +16,9 @@ map.set("name", "John"); let keys = map.keys(); *!* -// 错误:numbers.push 不是一个函数 +// Error: keys.push is not a function keys.push("more"); */!* ``` -为什么?我们如何修改代码让 `keys.push` 正常工作? +为什么?我们应该如何修改代码让 `keys.push` 工作? diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index f6abd8a090..768a50015c 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -14,12 +14,12 @@ 它的方法和属性如下: -- `new Map()` - 创建 `Map`。 +- `new Map()` - 创建 map。 - `map.set(key, value)` - 根据键存储值。 - `map.get(key)` - 根据键来返回值,如果 `map` 中不存在对应的 `key`,则返回 `undefined`。 - `map.has(key)` - 如果 `key` 存在则返回 `true`,否则返回 `false`。 -- `map.delete(key)` - 根据键来删除值。 -- `map.clear()` - 清空 `Map`。 +- `map.delete(key)` - 删除指定键的值。 +- `map.clear()` - 清空 map。 - `map.size` - 返回当前元素个数。 举个例子: @@ -47,7 +47,7 @@ alert( map.size ); // 3 所以我们应该使用 `map` 方法:`set` 和 `get` 等。 ``` -**Map 还可以使用对象作为键** +**Map 还可以使用对象作为键。** 例如: @@ -57,20 +57,20 @@ let john = { name: "John" }; // 存储每个用户的来访次数 let visitsCountMap = new Map(); -// john 是集合中的键 +// john 是 Map 中的键 visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` -使用对象作为键是 `Map` 最值得注意和重要的功能之一。对于字符串键,`Object`(普通对象)能正常使用,但对于对象键则不能。 +使用对象作为键是 `Map` 最值得注意和重要的功能之一。对于字符串键,`Object`(普通对象)也能正常使用,但对于对象键则不行。 我们来尝试一下: ```js run let john = { name: "John" }; -let visitsCountObj = {}; // 尝试使用对象 +let visitsCountObj = {}; // 尝试使用对象 visitsCountObj[john] = 123; // 尝试将 john 对象作为键 @@ -80,34 +80,32 @@ alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` -因为 `visitsCountObj` 是一个普通对象,它会将所有的键如 `john` 转换为字符串,所以我们得到字符键 `"[object Object]"`。这显然不是我们想要的结果。 +因为 `visitsCountObj` 是一个对象,它会将所有的键如 `john` 转换为字符串,所以我们得到字符串键 `"[object Object]"`。这显然不是我们想要的结果。 ```smart header="`Map` 是怎么比较键的?" - -`Map` 使用 [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero)算法来比较键是否相等。 它和严格等于 `===` 差不多,但区别是 `NaN` 被看成是等于 `NaN`。 所以 `NaN` 也可以被用作键。 +`Map` 使用 [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero) 算法来比较键是否相等。它和严格等于 `===` 差不多,但区别是 `NaN` 被看成是等于 `NaN`。所以 `NaN` 也可以被用作键。 这个算法不能被改变或者自定义。 - ``` ````smart header="链式调用" - -每一次调用 `map.set` 都会返回集合本身,所以我们可以”连续“调用: +每一次 `map.set` 调用都会返回 map 本身,所以我们可以进行“链式”调用: ```js map.set('1', 'str1') .set(1, 'num1') .set(true, 'bool1'); ``` +```` -## `Map` 迭代 +## Map 迭代 如果要在 `map` 里使用循环,可以使用以下三个方法: -- `map.keys()` - 返回键名的遍历器, -- `map.values()` - 返回键值的遍历器, -- `map.entries()` - 返回实体 `[key, value]` 的遍历器,默认在 `for..of` 中使用。 +- `map.keys()` - 遍历并返回所有的键(returns an iterable for keys), +- `map.values()` - 遍历并返回所有的值(returns an iterable for values), +- `map.entries()` - 遍历并返回所有的实体(returns an iterable for entries)`[key, value]`,`for..of` 在默认情况下使用的就是这个。 例如: @@ -118,42 +116,41 @@ let recipeMap = new Map([ ['onion', 50] ]); -// 迭代键(vegetables) +// 遍历所有的键(vegetables) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } -// 迭代值(amounts) +// 遍历所有的值(amounts) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } -// 迭代 [key, value] 对 -for (let entry of recipeMap) { // 效果跟 recipeMap.entries() 相同 - alert(entry); // cucumber,500(以此类推) +// 遍历所有的实体 [key, value] +for (let entry of recipeMap) { // 与 recipeMap.entries() 相同 + alert(entry); // cucumber,500 (and so on) } ``` ```smart header="使用插入顺序" - -迭代的顺序与键的插入顺序相同。不同于普通的 `Object`,`Map` 会保留键的插入顺序。 +迭代的顺序与插入值的顺序相同。与普通的 `Object` 不同,`Map` 保留了此顺序。 ``` -除此之外,`Map` 有个内置的 `forEach` 方法,跟 `Array` 类似: +除此之外,`Map` 有内置的 `forEach` 方法,与 `Array` 类似: ```js -// 对每个 (key, value) 键值对运行 forEach 函数 +// 对每个键值对 (key, value) 运行 forEach 函数 recipeMap.forEach( (value, key, map) => { - alert(`${key}: ${value}`); // cucumber: 500 等 + alert(`${key}: ${value}`); // cucumber: 500 etc }); ``` -## Object.entries:把对象转化为 `map` +## Object.entries:从对象创建 Map - 创建 `Map` 后,我们可以传入带有键值对的数组 (或其它可迭代的对象) 来进行初始化,像这样: +当创建一个 `Map` 后,我们可以传入一个带有键值对的数组(或其它可迭代对象)来进行初始化,如下所示: ```js run -// 包含 [key, value] 对的数组 +// 键值对 [key, value] 数组 let map = new Map([ ['1', 'str1'], [1, 'num1'], @@ -163,9 +160,9 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -如果我们想从一个已有的 plain object 来创建 `Map`,可以使用内置方法 [Object.entries(obj)](mdn:js/Object/entries),它返回一个对象的键值对数组,该数组符合 `Map` 集合的格式。 +如果我们想从一个已有的普通对象(plain object)来创建一个 `Map`,那么我们可以使用内建方法 [Object.entries(obj)](mdn:js/Object/entries),该返回对象的键/值对数组,该数组格式完全按照 `Map` 所需的格式。 -所以可以像下面这样利用一个对象来创建 `map` +所以可以像下面这样从一个对象创建一个 Map: ```js run let obj = { @@ -180,14 +177,14 @@ let map = new Map(Object.entries(obj)); alert( map.get('name') ); // John ``` -这里,`Object.entries` 返回一个含有键值对的数组:`[ ["name","John"], ["age", 30] ]`。这就是 `Map` 所需要的参数格式。 +这里,`Object.entries` 返回键/值对数组:`[ ["name","John"], ["age", 30] ]`。这就是 `Map` 所需要的格式。 -## Object.fromEntries:把 `map` 转化为对象 +## Object.fromEntries:从 Map 创建对象 -我们刚刚已经利用 `Object.entries(obj)` 把一个 plain object 转化成 `Map`。 +我们刚刚已经学习了如何使用 `Object.entries(obj)` 从普通对象(plain object)创建 `Map`。 -`Object.fromEntries` 方法的作用是相反的:给定一个具有 `[key, value]` 对的数组,它会根据给定数组生成对象: +`Object.fromEntries` 方法的作用是相反的:给定一个具有 `[key, value]` 键值对的数组,它会根据给定数组创建一个对象: ```js run let prices = Object.fromEntries([ @@ -201,9 +198,9 @@ let prices = Object.fromEntries([ alert(prices.orange); // 2 ``` -我们可以使用 `Object.fromEntries` 从 `Map` 中得到一个 plain object。 +我们可以使用 `Object.fromEntries` 从 `Map` 得到一个普通对象(plain object)。 -例如,我们存了数据在 `Map` 中,但是我们需要把它传给需要 plain object 的第三方代码。 +例如,我们在 `Map` 中存储了一些数据,但是我们需要把这些数据传给需要普通对象(plain object)的第三方代码。 我们来开始: @@ -214,41 +211,40 @@ map.set('orange', 2); map.set('meat', 4); *!* -let obj = Object.fromEntries(map.entries()); // 生成一个 plain object (*) +let obj = Object.fromEntries(map.entries()); // 创建一个普通对象(plain object)(*) */!* -// 完成! +// 完成了! // obj = { banana: 1, orange: 2, meat: 4 } alert(obj.orange); // 2 ``` -调用 `map.entries()` 将返回含有键值对的数组,这刚好是 `Object.fromEntries` 所需要的格式。 - -我们可以把带 `(*)` 的这一行写得更短: +调用 `map.entries()` 将返回键/值对数组,这刚好是 `Object.fromEntries` 所需要的格式。 +我们可以把带 `(*)` 这一行写得更短: ```js let obj = Object.fromEntries(map); // 省掉 .entries() ``` -上面的代码作用也是一样的,因为 `Object.fromEntries` 需要一个可迭代对象作为参数,而不一定是数组。`map` 的标准迭代会返回跟 `map.entries()` 一样的键值对。所以我们可以获得一个与 `map` 一样具有键值对的 plain object。 +上面的代码作用也是一样的,因为 `Object.fromEntries` 期望得到一个可迭代对象作为参数,而不一定是数组。并且 `map` 的标准迭代会返回跟 `map.entries()` 一样的键/值对。因此,我们可以获得一个普通对象(plain object),其键/值对与 `map` 相同。 ## Set -`Set` 是一个特殊类型的集合 - ”值的集合“ (没有键 ),它的每一个值只出现一次。 +`Set` 是一个特殊的类型集合 - “值的集合”(没有键),它的每一个值只能出现一次。 它的主要方法如下: - `new Set(iterable)` - 创建一个 `set`,如果提供了一个 `iterable` 对象(通常是数组),将会从数组里面复制值到 `set` 中。 - `set.add(value)` - 添加一个值,返回 set 本身 -- `set.delete(value)` - 删除值,如果 `value` 在调用的时候存在则返回 `true` ,否则返回 `false`。 +- `set.delete(value)` - 删除值,如果 `value` 在这个方法调用的时候存在则返回 `true` ,否则返回 `false`。 - `set.has(value)` - 如果 `value` 在 set 中,返回 `true`,否则返回 `false`。 -- `set.clear()` - 清空集合。 -- `set.size` - 返回元素的个数。 +- `set.clear()` - 清空 set。 +- `set.size` - 返回元素个数。 -它的主要特点是重复使用同一个值调用 `set.add(value)` 并不会发生什么改变。这就是 `Set` 里面的每一个值只出现一次的原因。 +它的主要特点是,重复使用同一个值调用 `set.add(value)` 并不会发生什么改变。这就是 `Set` 里面的每一个值只出现一次的原因。 -例如,我们有客人来访,想记住他们每一个人。但是已经访问过的客人不应重复记录。每个访客必须只被“计数”一次。 +例如,我们有客人来访,我们想记住他们每一个人。但是已经来访过的客人再次来访,不应造成重复记录。每个访客必须只被“计数”一次。 `Set` 可以帮助我们解决这个问题: @@ -266,69 +262,69 @@ set.add(mary); set.add(john); set.add(mary); -// set 只保留单一值 +// set 只保留不重复的值 alert( set.size ); // 3 for (let user of set) { - alert(user.name); // John (然后 Pete 和 Mary) + alert(user.name); // John(然后 Pete 和 Mary) } ``` -替代 `Set` 的场景可以是一个用户数组,用 [arr.find](mdn:js/Array/find) 在每次插入时检查是否重复。但是这样会使性能变差,因为这个方法会遍历整个数组来检查每个元素。`Set` 内部对唯一性检查进行了更好的优化。 +`Set` 的替代方法可以是一个用户数组,用 [arr.find](mdn:js/Array/find) 在每次插入值时检查是否重复。但是这样性能会很差,因为这个方法会遍历整个数组来检查每个元素。`Set` 内部对唯一性检查进行了更好的优化。 -## Set 迭代 +## Set 迭代(iteration) -我们可以使用 `for..of` 或 `forEach` 来循环 `Set` 集合: +我们可以使用 `for..of` 或 `forEach` 来遍历 Set: ```js run let set = new Set(["oranges", "apples", "bananas"]); for (let value of set) alert(value); -// 跟 forEach 方法相同: +// 与 forEach 相同: set.forEach((value, valueAgain, set) => { alert(value); }); ``` -注意一件有趣的事情。`forEach` 的回调函数有三个参数:一个 `value`,然后是*相同值* `valueAgain`,最后才是目标对象。没错,相同的值在参数里出现了两次。 +注意一件有趣的事儿。`forEach` 的回调函数有三个参数:一个 `value`,然后是 **同一个值** `valueAgain`,最后是目标对象。没错,同一个值在参数里出现了两次。 -这是为了兼容 `Map` 在回调里传入 `forEach` 函数后有三个参数。当然这看起来有点奇怪。但是这对在特定情况下比如使用 `Set` 代替 `Map` 的时候有帮助,反之亦然。 +`forEach` 的回调函数有三个参数,是为了与 `Map` 兼容。当然,这看起来确实有些奇怪。但是这对在特定情况下轻松地用 `Set` 代替 `Map` 很有帮助,反之亦然。 `Map` 中用于迭代的方法在 `Set` 中也同样支持: -- `set.keys()` - 返回一个包含值的可迭代对象, -- `set.values()` - 跟 `set.keys()` 作用相同,为了兼容 `Map`, -- `set.entries()` - 返回一个包含 `[value, value]` 对的可迭代对象,它的存在也是为了兼容 `Map`。 +- `set.keys()` - 遍历并返回所有的值(returns an iterable object for values), +- `set.values()` - 与 `set.keys()` 作用相同,这是为了兼容 `Map`, +- `set.entries()` - 遍历并返回所有的实体(returns an iterable object for entries)`[value, value]`,它的存在也是为了兼容 `Map`。 ## 总结 -`Map` - 是一个键值对集合。 +`Map` - 是一个带键的数据项的集合。 方法和属性如下: -- `new Map([iterable])` - 创建空的 map,可选择带有 `[key,value]` 对的 `iterable`(例如数组)对象来进行初始化 。 -- `map.set(key, value)` - 存储对应的键值。 -- `map.get(key)` - 根据键来返回值,如果键不存在 map 里就返回 `undefined`。 +- `new Map([iterable])` - 创建 map,可选择带有 `[key,value]` 对的 `iterable`(例如数组)来进行初始化。 +- `map.set(key, value)` - 根据键存储值。 +- `map.get(key)` - 根据键来返回值,如果 `map` 中不存在对应的 `key`,则返回 `undefined`。 - `map.has(key)` - 如果 `key` 存在则返回 `true`,否则返回 `false`。 - `map.delete(key)` - 删除指定键的值。 - `map.clear()` - 清空 map 。 -- `map.size` - 返回当前全部元素的个数。 +- `map.size` - 返回当前元素个数。 -跟 plain object `Object` 最大的不同点是: +与普通对象 `Object` 的不同点: -- 任何键、对象都可以作为键, +- 任何键、对象都可以作为键。 - 有其他的便捷方法,如 `size` 属性。 `Set` - 是一组唯一值的集合。 方法和属性: -- `new Set([iterable])` - 创建空的 set,可选的 `iterable`(例如数组)对象来进行初始化。 -- `set.add(value)` - 添加一个值(如果值不存在),返回 set 本身。 -- `set.delete(value)` - 删除值,如果 `value` 在调用的时候存在则返回 `true`,否则返回 `false`。 -- `set.has(value)` - 如果值在集合中,则返回 `true`,否则返回 `false`。 -- `set.clear()` - 清空集合。 -- `set.size` - 返回当前全部元素的个数。 +- `new Set([iterable])` - 创建 set,可选择带有 `iterable`(例如数组)来进行初始化。 +- `set.add(value)` - 添加一个值(如果 `value` 存在则不做任何修改),返回 set 本身。 +- `set.delete(value)` - 删除值,如果 `value` 在这个方法调用的时候存在则返回 `true` ,否则返回 `false`。 +- `set.has(value)` - 如果 `value` 在 set 中,返回 `true`,否则返回 `false`。 +- `set.clear()` - 清空 set。 +- `set.size` - 元素的个数。 -在 `Map` 和 `Set` 中迭代总是按照插入的顺序来执行的,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其顺序来获取元素。 +在 `Map` 和 `Set` 中迭代总是按照值插入的顺序进行的,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其编号来获取元素。