您的位置:首页 > Web前端 > JavaScript

JavaScript拆分字符串时产生空字符的原因

2015-01-05 14:15 399 查看


问题描述

使用JavaScript的
split
方法拆分字符串时出现一些空字符串
""
,尤其是当使用正则表达式作为分隔符的时候。

相关问题

javascript正则表达式对字符串分组时产生空字符串组?

在上面这个问题中,题主使用正则表达式对字符串进行分割时产生了多个空字符串
""
,代码如下:

'张sdf四上法asdf翁芬aa33网s'.split(/([\u4e00-\u9fa5]{1})/gi);
//输出["", "张", "sdf", "四", "", "上", "", "法", "asdf", "翁", "", "芬", "aa33", "网", "s"]

那么,产生这些空字符串的原因是什么?

问题分析

在Google上搜索了一番,发现相关的结果并不多,即便有,详细解释的也不多,大概的说了一下,然后就给出了一个ECMAScript规范的链接。看来要想知道真正的原因,就只能硬着头皮看规范了。

相关标准

那么,接下来,按照国际惯例,先上ECMAScript的标准镇楼。

String.prototype.split (separator, limit)

这个章节详细介绍了
split
方法的执行步骤,如果感兴趣的话可以一步一步的认真看完,我在这里只把和产生空字符串相关的步骤拿出来解释一下,不当之处,欢迎大家提出。

相关步骤

摘取部分步骤:



整个过程中最主要的步骤是第13步这个循环,而这个循环主要做的事情如下:

定义
p
,
q
的值,每一次循环开始的时候
p
q
的值是相同的(该步骤在循环之外);

调用
SplitMatch(S, q, R)
这个方法对字符串进行拆分;
根据返回结果的不同,执行不同的分支,主要分支为分支

分支
又分成了8个小步用来将返回的结果填充到事先定义好的数组
A

在这个8小步中,步骤
1
的作用是返回原始字符串的一个子串,开始位置是
p
(包含在内),结束位置是
q
(不包含在内),注意:在这一步中会产生空字符串,我将其标记为
截取字符串
,方便下文引用。

将上一步的子串添加到数组
A

接下来的几步是更新相关的变量,继续下一次循环。(步骤7的作用是将正则表达式中的捕获分组保存到数组
A
中,和产生空字符串无关)

SplitMatch(S, q, R)

接下来,我们需要了解一下
SplitMatch(S, q, R)
这个方法做了些什么事。这个方法在
split
规范中的下方有提及。它主要做的事是,根据分隔符(
separator
)的类型进行相应的操作:

如果分隔符是
RegExp
类型的,调用
RegExp
的内部方法
[[Match]]
来对字符串进行匹配,如果匹配失败,返回
failure
,否则,返回一个
MatchResult
类型的结果。

如果分隔符是字符串,进行匹配判断,失败返回
failure
,成功返回
MatchResult
类型的结果。

MatchResult

上面的步骤中又引出了一个
MatchResult
类型的变量。通过查文档发现,该类型的变量有两个属性
endIndex
captures
endIndex
的值是字符串匹配的位置加上1,
captures
可以理解为一个数组,当分隔符为正则表达式时,它里面的元素是分组捕获的值;当分隔符为字符串时,它为一个空数组。

接下来

我们从上面的步骤可以看出,分割的字符串是在
截取字符串
这一步骤中产生的(正则表达式的分组捕获除外)。它的作用是截取指定开始(包含在内)和结束位置(不包含在内)之间的字符串,那它什么时候会返回
""
呢?有一种特殊情况是开始位置和结束位置的值相等,这只是猜想而已,因为该规范没有给出截取字符串的规范步骤。

都走到这里了,为什么不再往前走一步呢?

于是,我试着搜索了一些V8的源码,看看能不能找到具体的实现方法。确实找到了相关的代码,源码链接

这里摘取其中一部分:

function StringSplitJS(separator, limit) {
...
...
//分隔符是字符串的情况
if (!IS_REGEXP(separator)) {
var separator_string = TO_STRING_INLINE(separator);

if (limit === 0) return [];

// ECMA-262 says that if separator is undefined, the result should
// be an array of size 1 containing the entire string.
if (IS_UNDEFINED(separator)) return [subject];

var separator_length = separator_string.length;

//分隔符是空字符串,直接返回了字符数组
if (separator_length === 0) return %StringToArray(subject, limit);

var result = %StringSplit(subject, separator_string, limit);

return result;
}

if (limit === 0) return [];

// 分隔符是正则表达式的情况,调用StringSplitOnRegExp
return StringSplitOnRegExp(subject, separator, limit, length);
}

//此处省略若干代码

我在代码中发现,在填充数组的时候会调用
%_SubString
这个方法来截取字符串,可惜的是我没有找到他的相关定义,如果有找到的同学欢迎告知。但是,我发现JavaScript中
substring
这个方法所对应的
StringSubstring
这个方法会调用
%_SubString
这个方法,并将其结果返回。那么如果
'abc'.substring(1,1)
返回
""
,则表明
%_SubString
这个方法在开始位置和结束位置相同的时候会返回
""
,结果大家一试便知。

那么,什么时候会出现开始位置等于结束位置(即
q === p
)的情况呢?我按照上面的步骤一步一步的进行分析,最终发现:

当原始字符串
S
匹配过一次分隔符之后,紧接着,字符串
S
的下一个位置还匹配分隔符。如:
'abbbc'.split('b')
'abbbc'.split(/(b){1}/)


另一种情况是字符串开头的一个或几个字符匹配分隔符。如:
'abc'.split('a')
'abc'.split(/ab/)


还有一种情况是字符串结尾的一个或几个字符串匹配分隔符,与之相关的步骤是第14步。

如:
'abc'.split('c')
'abc'.split(/bc/)

此外,当使用正则表达式作为分隔符的时候,返回的结果中还有可能出现
undefined


如:
'abc'.split(/(d)*/)


回过头来再看看开头的那个例子,是不是满足上面几种情况?

题外话

这是我第一次这么仔细的看ECMAScript的标准规范,看的过程确实很痛苦,但明白之后就感觉很痛快了。也感谢题主提出的这个问题,以及追问。

顺便提一句,正则表达式作为分隔符时,
global
修饰符
g
是会被忽略的,这也算是一次额外的收获。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: