先记住这个答案
数组本身没有特殊的字符串形式,把它转成字符串时,JavaScript 会调用 Array.prototype.toString(),而这个方法内部就是执行一次不带参数的 join()。join() 默认用逗号把每个元素转成字符串后连起来。空数组一个元素都没有,连出来什么也没有,所以是空字符串;[1, 2] 连出来就是 '1,2'。
- 数组转字符串就是
arr.join(',') - 空数组得到
'',这是很多隐式转换题的起点 join把空值与空位转成空串- 嵌套数组会递归拍平,结构信息丢失
各种数组转出来是什么
先看一组结果,再回头讲规则,这些基本就是面试会考的全部情形。
console.log(JSON.stringify(String([]))) // 空数组
console.log(String([1, 2])) // 普通数组
console.log(String([null, undefined, 3])) // null 和 undefined
console.log(String([1, [2, [3, 4]]])) // 嵌套数组
console.log(JSON.stringify([] + [])) // 两个空数组相加
console.log([1, 2] + 1) // 数组和数字相加
const arr = [1, 2]
arr.join = () => 'custom' // 改写 join
console.log(String(arr))查看输出与解释
""
1,2
,,3
1,2,3,4
""
1,21
customnull 和 undefined 被 join 当成空串处理,所以 [null, undefined, 3] 只剩两个逗号加一个 3。嵌套数组里的每个子数组又会调用自己的 toString(),层层展开后全部用逗号拼成一串,原来的层级关系看不出来了。最后一个例子说明 toString() 确实是在调 join:把实例上的 join 换掉,结果就跟着变。
什么时候会触发这个转换
显式的 String(arr)、模板字符串 ${arr} 都会触发。更常见的是隐式场景:arr + ''、arr + 1 这类加法,以及 == 两边一个是数组一个是原始值的比较。
加法遇到对象时,会先做 ToPrimitive,也就是把对象转成原始值。对普通数组来说,先试 valueOf(),它返回数组自身,不是原始值;于是再试 toString(),拿到字符串。只要有一边是字符串,+ 就做拼接,所以 [1, 2] + 1 是 '1,21' 而不是数字。
[] == 0 为 true 也是这条路:空数组先变成 '',空字符串再转数字是 0。很多「数组等于某某」的奇怪题目,第一步都是这里。
容易答错的地方
- 以为数组转字符串会带方括号
String([1, 2])是'1,2',不是'[1,2]'。带方括号的是JSON.stringify([1, 2]),两者用途完全不同,日志里要看结构应该用后者。- 以为
null元素会变成'null' String(null)是'null',但在数组里join对null和undefined做了特殊处理,直接当空串。空位(比如[1, , 3]中间那个)也一样是空串。- 以为
toString和join是两套逻辑 - 数组的
toString()没有自己的拼接逻辑,就是转调join()。区别只是join能传分隔符,比如arr.join('-')。
面试官还会怎么问?
[] + {} 和 {} + [] 结果一样吗?
在表达式位置都是 '[object Object]',因为空数组变 '',对象变 '[object Object]',再拼接。但 {} + [] 如果写在语句开头,开头的 {} 会被解析成空代码块,实际执行的是 +[],结果是数字 0。现在的浏览器控制台会尽量把输入当表达式处理,所以在控制台里不一定能复现这个 0。
数组里有 Symbol 时转字符串会怎样?
会抛 TypeError。join 要把每个元素转成字符串,而 Symbol 不允许隐式转字符串。需要展示时要先 map 成 String(s) 或读 s.description。
想要保留结构的字符串,应该用什么?
用 JSON.stringify。它会输出方括号和嵌套层级,字符串元素带引号,null 保留为 null。不过它会把 undefined 元素变成 null,BigInt 元素会直接抛错,这些要额外处理。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。