摘要:问题描述使用的方法拆分字符串时出现一些空字符串,尤其是当使用正则表达式作为分隔符的时候。如果分隔符是字符串,进行匹配判断,失败返回,成功返回类型的结果。如,另一种情况是字符串开头的一个或几个字符匹配分隔符。
问题描述
使用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是会被忽略的,这也算是一次额外的收获。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/85303.html
摘要:三攻击分类反射型又称为非持久性跨站点脚本攻击,它是最常见的类型的。存储型又称为持久型跨站点脚本,它一般发生在攻击向量一般指攻击代码存储在网站数据库,当一个页面被用户打开的时候执行。例如,当错误,就会执行事件利用跨站。 一、简介 XSS(cross site script)是指恶意攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些代码,嵌入到web页面中去。使别...
摘要:接下来的部分将讨论如何确保事件循环和工作池的公平调度。不要阻塞事件循环事件循环通知每个新客户端连接并协调对客户端的响应。 你应该阅读本指南吗? 如果您编写比命令行脚本更复杂的程序,那么阅读本文可以帮助您编写性能更高,更安全的应用程序。 在编写本文档时,主要是基于Node服务器。但里面的原则也适用于其它复杂的Node应用程序。在没有特别说明操作系统的情况下,默认为Linux。 TL; D...
摘要:对于性能来说真的非常糟糕。的推出使网页性能提高了大约,所有这些都不需要开发人员参与。这意味着和中的存在错误。将放在中这个最终策略是一个相对较新的策略,对感知性能和渐进式渲染有很大好处。 CSS对于呈现页面至关重要 - 在找到,下载和解析所有CSS之前,浏览器不会开始呈现 - 因此我们必须尽可能快地将其加载到用户的设备上。 关键路径上的任何延迟都会影响我们的开始渲染并让用户看到空白屏幕。...
阅读 935·2021-11-17 09:33
阅读 400·2019-08-30 11:16
阅读 2454·2019-08-29 16:05
阅读 3322·2019-08-29 15:28
阅读 1375·2019-08-29 11:29
阅读 1928·2019-08-26 13:51
阅读 3368·2019-08-26 11:55
阅读 1185·2019-08-26 11:31