Javascript 关于包含 1...N 的数组使用扩展运算符的异常行为
Javascript unusual behavior regarding Array containing 1...N using spread operator
在Javascript中,我用展开运算符制作了这个简单的升序数组。
[...Array(5).keys()]
// printed list
[0, 1, 2, 3, 4]
但我只是加了 1 并生成了这个意外的数组。
(我只是以为会出错)
[...Array(5).keys() + 1]
// printed list
["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]
我想知道是什么导致了这种行为。
正如您输入的那样 [...(Array(5).keys() + 1)]
这意味着 +
优先于 ...
首先,计算 ...
右边的表达式。也就是说,Array(5).keys() + 1
被评估,然后评估的结果被传播。对表达式进行分组可能会使事情变得更清楚:
[...(Array(5).keys() + 1)]
所以,上面,我们从计算Array(5).keys() + 1
的结果开始。 +
运算符在原始操作数之间工作。当您执行 Array(5).keys()
时,您会得到一个未归类为基元的迭代器。因此,JS 将尝试将其转换为原语。当 JS 尝试在此上下文中将迭代器转换为原语时,它会尝试:
- 调用迭代器的
Symbol.toPrimitive
方法,因为这个方法不存在,它会继续尝试下一步
- 调用迭代器的
.valueOf()
方法。这成功了,但是它 returns 迭代器返回,这不是原语,所以我们进入下一步
- 调用
.toString()
方法。这导致非对象类型(即:字符串),因此这是成功的。
当迭代器的 .toString()
方法被调用时,它被评估为 "[object Array Iterator]"
。因为这随后与数字 1
相加,我们将字符串与数字连接(而不是相加),给出:
[..."[object Array Iterator]1"]
由于字符串是可迭代的,展开语法将使用字符串的迭代器循环遍历字符串中的代码点,从而使字符串中的每个字符都成为它自己的元素:
["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]
为了向迭代器中的每个项目添加一个,您可以使用 Array.from()
它可以采用迭代器,并对每个元素应用映射函数:
const res = Array.from(Array(5).keys(), i => i+1);
console.log(res);
由于 Array.from() 的参数不必是迭代器,您也可以只使用 Array(5)
并将索引用作要添加到的值:
const res = Array.from(Array(5), (_,i) => i+1);
console.log(res);
当您添加 + 1 时,它将处理来自 ...Array(5).keys() 的 return 作为字符串。
看看这些额外的例子:
console.log(数组(5).keys())
输出:
[对象数组迭代器]
console.log(数组(5).keys() + 1)
输出:
[对象数组迭代器]1
console.log(...数组(5).keys() + 1)
输出:
[ ob j e c t A r r a y I t e r a to or ] 1
在 JavaScript 中阅读有关使用不同运算符时的类型转换的更多信息
为了简化答案,如果你尝试做Object() + 1
,你会得到"[object Object]1"
,它不能转换为数字,JS将Object()
转换为一个字符串,你可以通过 String(Object())
,
在您的示例中,Array(5).keys()
是一个继承自 Array Iterator 的对象,因此当转换为字符串时,您会得到 "[object Array Iterator]"
,当然加 1 会得到 "[object Array Iterator]1"
,它现在是一个字符串,如果您使用扩展运算符,它将被拆分为字符,这是您的示例的预期结果
相反,要加 1,您可以这样做:[...Array(5).keys()].map(i=> i+1)
在Javascript中,我用展开运算符制作了这个简单的升序数组。
[...Array(5).keys()]
// printed list
[0, 1, 2, 3, 4]
但我只是加了 1 并生成了这个意外的数组。 (我只是以为会出错)
[...Array(5).keys() + 1]
// printed list
["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]
我想知道是什么导致了这种行为。
正如您输入的那样 [...(Array(5).keys() + 1)]
这意味着 +
优先于 ...
首先,计算 ...
右边的表达式。也就是说,Array(5).keys() + 1
被评估,然后评估的结果被传播。对表达式进行分组可能会使事情变得更清楚:
[...(Array(5).keys() + 1)]
所以,上面,我们从计算Array(5).keys() + 1
的结果开始。 +
运算符在原始操作数之间工作。当您执行 Array(5).keys()
时,您会得到一个未归类为基元的迭代器。因此,JS 将尝试将其转换为原语。当 JS 尝试在此上下文中将迭代器转换为原语时,它会尝试:
- 调用迭代器的
Symbol.toPrimitive
方法,因为这个方法不存在,它会继续尝试下一步 - 调用迭代器的
.valueOf()
方法。这成功了,但是它 returns 迭代器返回,这不是原语,所以我们进入下一步 - 调用
.toString()
方法。这导致非对象类型(即:字符串),因此这是成功的。
当迭代器的 .toString()
方法被调用时,它被评估为 "[object Array Iterator]"
。因为这随后与数字 1
相加,我们将字符串与数字连接(而不是相加),给出:
[..."[object Array Iterator]1"]
由于字符串是可迭代的,展开语法将使用字符串的迭代器循环遍历字符串中的代码点,从而使字符串中的每个字符都成为它自己的元素:
["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]
为了向迭代器中的每个项目添加一个,您可以使用 Array.from()
它可以采用迭代器,并对每个元素应用映射函数:
const res = Array.from(Array(5).keys(), i => i+1);
console.log(res);
由于 Array.from() 的参数不必是迭代器,您也可以只使用 Array(5)
并将索引用作要添加到的值:
const res = Array.from(Array(5), (_,i) => i+1);
console.log(res);
当您添加 + 1 时,它将处理来自 ...Array(5).keys() 的 return 作为字符串。 看看这些额外的例子:
console.log(数组(5).keys()) 输出: [对象数组迭代器]
console.log(数组(5).keys() + 1) 输出: [对象数组迭代器]1
console.log(...数组(5).keys() + 1) 输出: [ ob j e c t A r r a y I t e r a to or ] 1
在 JavaScript 中阅读有关使用不同运算符时的类型转换的更多信息
为了简化答案,如果你尝试做Object() + 1
,你会得到"[object Object]1"
,它不能转换为数字,JS将Object()
转换为一个字符串,你可以通过 String(Object())
,
在您的示例中,Array(5).keys()
是一个继承自 Array Iterator 的对象,因此当转换为字符串时,您会得到 "[object Array Iterator]"
,当然加 1 会得到 "[object Array Iterator]1"
,它现在是一个字符串,如果您使用扩展运算符,它将被拆分为字符,这是您的示例的预期结果
相反,要加 1,您可以这样做:[...Array(5).keys()].map(i=> i+1)