JS数组转字符串:join()与toString()的区别及用法

作者:青柠

时间:2026-10-30

浏览:284

深入解析 JavaScript 数组转字符串中 join() 与 toString() 的区别、用法细节与底层原理。涵盖参数控制、多维数组转换、null/undefined 边界处理以及两者的规范关联。

文章目录
在 JavaScript 中,将数组转换为字符串最核心的方法是 join() 与 toString()。两者的核心区别在于:toString() 只能使用固定的半角逗号(,)连接元素,且不接收任何自定义参数;而 join([separator]) 则允许指定任意字符作为分隔符(包括空字符或特定标点),未指定参数时则默认以逗号连接。从底层规范来看,数组原型的 toString() 本质上是直接委托调用 this.join() 实现的。
JS数组转字符串:join()与toString()的区别及用法
图 0:JavaScript 数组转字符串核心方法快速对比

一、toString() 的用法与默认行为

toString() 是定义在 Array.prototype 上的内置方法,属于 JavaScript 中所有对象通用 toString 规范在数组上的特化实现。它的作用是遍历调用数组中的每一项,并将其以英文半角逗号拼接为一个整体字符串。

1. 语法形式与基础示例

toString() 不接受任何有效参数。即使向其传入分隔符参数,也会被完全忽略,转换结果依然固定使用半角逗号:

// 语法形式:arr.toString()
const fruits = ['Apple', 'Banana', 'Cherry'];

// 基础调用
const defaultResult = fruits.toString();

// 尝试传入参数(参数会被直接忽略)
const ignoredParamResult = fruits.toString('-');
示例运行输出 defaultResult: "Apple,Banana,Cherry"
ignoredParamResult: "Apple,Banana,Cherry"

从上述输出可以看出,fruits.toString('-') 并没有生成带连字符的字符串,这是很多初学者容易误解的地方。toString() 的函数签名只满足通用的对象转字符串接口,没有任何定制参数的能力。

2. 原型委托机制与底层调用链

在 ECMAScript 规范定义中,Array.prototype.toString() 的执行过程非常明确:它首先检查当前调用上下文是否存在名为 join 的属性,并且该属性是否为一个可执行的函数(IsCallable(join))。如果是函数,则直接调用并返回 this.join() 的执行结果;只有当 join 属性不存在或不是函数时,才会回退到 Object.prototype.toString.call(this),输出 "[object Array]"。

JS数组转字符串:join()与toString()的区别及用法
图 1:Array.prototype.toString 原型委托与回退机制

这一规范特征意味着:如果手动修改了某个数组对象的 join 方法,那么其 toString() 的行为也会随之改变:

const list = ['A', 'B', 'C'];

// 重写该实例自身的 join 方法
list.join = function() {
  return this.slice().reverse().join('->');
};

const res = list.toString();
示例运行输出 res: "C->B->A"

二、join() 的灵活性与分步操作

与 toString() 相比,join([separator]) 提供了对分隔符的完全控制权。它是日常数据处理、URL 查询字符串拼接、无缝字符组合以及列表排版的核心工具。

1. 分隔符参数的具体表现

join() 接收一个可选的分隔符参数 separator,根据传入参数的不同,具体表现如下:

  • 省略参数或传入 undefined:采用默认的半角逗号 "," 进行拼接,效果等同于 toString()。
  • 传入空字符串 "":各元素紧密连接,中间没有任何间隔字符。常用于字符数组逆向重组为单词。
  • 传入自定义字符串或符号:如破折号 "-"、空格 " "、斜杠 "/" 或换行符 " "。
  • 传入非字符串类型:系统会自动将其转换为字符串。例如传入数值 0 会变成 "0" 连接,传入 null 会被当作字符串 "null" 作为连接符。
const letters = ['j', 'a', 'v', 'a'];

// 1. 省略参数:默认逗号
const withDefault = letters.join();

// 2. 传入 undefined:依然是默认逗号
const withUndefined = letters.join(undefined);

// 3. 传入空字符串:无缝紧密拼接
const word = letters.join('');

// 4. 传入自定义连接符
const dashed = letters.join(' - ');

// 5. 传入非字符串值(数字 0)
const withZero = letters.join(0);
示例运行输出 withDefault: "j,a,v,a"
withUndefined: "j,a,v,a"
word: "java"
dashed: "j - a - v - a"
withZero: "j0a0v0a"

2. join() 内部四阶段执行流水线

为了准确理解 join() 的运作方式,了解其内部执行流水线至关重要。它按固定步骤顺序对数组进行只读扫描和拼接,整个操作绝不会修改原数组。

JS数组转字符串:join()与toString()的区别及用法
图 2:Array.prototype.join() 内部四阶段执行流水线
注意提醒:join() 与 toString() 均属于“纯读取”方法,执行完毕后返回的是全新的字符串对象,原始数组及其内部元素保持原样,不会被修改。

三、边界情况与特殊元素处理

在实际业务开发中,数组中往往不仅包含标准的基础数值或简短文字,还可能存在 null、undefined、稀疏空位、嵌套数组以及复杂对象。这两种方法在处理这类元素时有着相同的规范策略。

JS数组转字符串:join()与toString()的区别及用法
图 3:特殊数据类型与边界情况转换表现对照

1. null 与 undefined 的处理

根据 ECMAScript 规范,当数组中遍历到 null 或 undefined 时,它们既不会报错,也不会被转为字面量的 "null" 或 "undefined",而是统一转换为空字符串 "":

const data = ['Item', null, 'Target', undefined, 'End'];

const strByToString = data.toString();
const strByJoin = data.join('-');
示例运行输出 strByToString: "Item,,Target,,End"
strByJoin: "Item--Target--End"

注意观察输出中的连续分隔符(,, 与 --),这表明数组项的位置依然被保留,只是其内容为空字符串。

2. 稀疏数组与空位(Empty Slots)

如果在定义数组时遗留了逗号而形成稀疏数组(如 [1, , 3]),在转为字符串时,未分配值的索引位置会被按照 undefined 同样对待,同样转换为空字符串:

const sparse = [10, , 30];

const sparseToString = sparse.toString();
const sparseJoin = sparse.join('/');
示例运行输出 sparseToString: "10,,30"
sparseJoin: "10//30"

3. 嵌套多维数组与内层递归

这是开发中最容易忽略的细节:无论外层使用的是 toString() 还是 join(' - '),当遍历到内层嵌套数组时,内层数组都会默认调用其自身的 toString() 方法进行扁平化展开:

const matrix = [1, [2, 3], 4];

// 外层 toString()
const matrixToString = matrix.toString();

// 外层 join('-')
const matrixJoin = matrix.join('-');
示例运行输出 matrixToString: "1,2,3,4"
matrixJoin: "1-2,3-4"

可以看到,matrixJoin 的输出并不是 "1-2-3-4",而是 "1-2,3-4"。外层传递的 "-" 连接符无法自动渗透进内层数组。如果需要对多维数组统一使用自定义连接符,必须先使用 arr.flat(Infinity) 进行拍平,再调用 join():

const flatResult = [1, [2, 3], 4].flat(Infinity).join('-');
示例运行输出 flatResult: "1-2-3-4"

4. 数组元素为对象

如果数组中包含普通 JavaScript 对象,元素在转换时会调用 Object.prototype.toString(),结果将是 "[object Object]":

const users = [{ name: 'Alice' }, { name: 'Bob' }];

const userStr = users.join(' & ');
示例运行输出 userStr: "[object Object] & [object Object]"

若要将对象数组转换为具有可读性的字符串,应先使用 map() 提取指定字段,或使用 JSON.stringify() 处理后再进行 join()。

四、选型建议与总结

在明确了两者的原理与差异后,开发者可以在不同的业务场景下做出清晰、无歧义的选型决策。

JS数组转字符串:join()与toString()的区别及用法
图 4:方法选型决策树与核心特性总结矩阵

1. 选型对照清单

功能维度 toString() join([separator])
自定义连接符 不支持(固定为逗号 ",") 支持任意字符、空串或多字符标点
参数处理 忽略传入的任何参数 接收 1 个可选的分隔符参数
隐式类型转换 在字符串拼接(如 `${arr}` 或 arr + '')时自动触发 不会隐式触发,需显式编码调用
空值元素表现 null、undefined 转为空字符串 null、undefined 转为空字符串
规范实现关系 底层直接委托调用 this.join() 独立完整的抽象遍历操作
常用推荐场景 快速打印、断点调试、日志排查 业务字符串拼装、URL 参数组装、文字排版

2. 最佳实践准则

  1. 业务代码优先使用 join():即使需要逗号连接,也推荐显式写为 arr.join(',') 或 arr.join()。这种写法传达给阅读者的意图非常明确——“此处正在执行数组的字符串序列化与组装”,避免与对象的通用类型转换语义混淆。
  2. 需要完全无缝拼接时必须用 join(''):例如反转字符串 str.split('').reverse().join(''),这是 toString() 完全无法胜任的场景。
  3. 理解隐式转换背后的机制:当在模板字符串中使用 `Items: ${arr}` 时,引擎在底层调用的是 arr.toString(),而 toString() 又委托了 join(),最终产出逗号分隔的文本。了解这一连串的委托过程,有助于在出现非预期输出时迅速定位根因。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章