先记住这个答案
数组的 toString() 内部直接调用 this.join(),所以不传参数时两者结果完全一样,都是用逗号把元素连起来。join 多了一个能力:可以指定分隔符,比如 join('-') 或 join('')。转换时 null 和 undefined 会变成空字符串,但分隔符还保留着。嵌套数组的结果看起来像被展平了,实际上是内层数组也被转成了逗号连接的字符串,原数组的结构一点没变。
toString()等于不传参数的join()join能换分隔符,只影响最外层null和undefined在结果里变成空字符串- 转字符串会丢类型和层级,不能当序列化用
嵌套、空值和自定义 join
下面这个数组里既有嵌套数组,又有 null 和 undefined,最后还演示了改写 join 对 toString 的影响。
const values = [[1, 2], null, undefined, 3]
console.log(values.join('|')) // 外层用竖线
console.log(values.toString()) // 等于 join()
console.log(values.length, Array.isArray(values[0])) // 数组本身没变
const custom = [1, 2]
custom.join = () => 'custom text' // 改写实例上的 join
console.log(custom.toString()) // toString 调用的是当前的 join查看输出与解释
1,2|||3
1,2,,,3
4 true
custom text第一行里外层用了竖线,内层 [1, 2] 还是逗号,因为内层数组是自己调用 toString() 转成 "1,2" 的,外层的分隔符传不进去。null 和 undefined 变成了空字符串,所以能看到连续的竖线。第三行说明数组还是 4 项,第一项还是数组,转字符串没有改动任何结构。
最后一行说明 toString() 不是固定执行内置的 join,而是调用对象上当前的 join 方法。如果 join 被改成一个不是函数的值,toString() 会退回到 Object.prototype.toString,得到 "[object Array]"。
为什么 join 的结果不能当数据格式
join 的结果是给人看的,信息已经丢了。比如 [1, 2] 和 ['1,2'] 转出来都是 "1,2"。null 和 undefined 转出来是空,空字符串和空位也是空。拿到字符串再 split(','),没法知道原来是数字还是字符串,有没有嵌套。
- 需要还原就用 JSON
- 要把数组存进
localStorage、放进 URL 或者发给后端,用JSON.stringify。[[1, 2], null, 3]转成 JSON 是[[1,2],null,3],层级和类型都保留,JSON.parse能原样还原。 导出 CSV 也不能只靠join(',')。字段里本来就有逗号、引号或换行时,要按 CSV 规则用双引号包起来并转义,否则表格软件会把一个字段拆成好几列。
容易答错的地方
- 说
toString()把嵌套数组展平了 - 得到的只是一个字符串,原数组还是嵌套的。真正要展平数组,用
flat(),比如[[1, 2], 3].flat()得到[1, 2, 3],这才是一个新的一维数组。 - 以为
join('')能去掉所有逗号 join('')只去掉最外层的分隔符。[[1, 2], 3].join('')的结果是"1,23",内层数组转换时产生的逗号还在。想全部去掉,先flat(Infinity)再join('')。
面试官还会怎么问?
[] + [] 和 [] + {} 分别是什么?
+ 两边是对象时会先转成原始值,数组转出来走的就是 toString()。空数组转成空字符串,所以 [] + [] 是空字符串,而 [] + {} 是 "[object Object]"。
String([null]) 和 String(null) 一样吗?
不一样。String(null) 是 "null",而数组里的 null 在 join 时会变成空字符串,所以 String([null]) 是 ""。undefined 同理。
模板字符串里直接放数组会怎样?
${arr} 会调用数组的 toString(),得到逗号连接的结果。想要别的格式,比如用顿号或换行连接,要自己写 arr.join('、')。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。