String.prototype.split不为人知的另外一面

1,082 阅读3分钟

我们平时使用会使用 split 把字符串切割为数组,但实际上,split还可以使用正则表达式作为定界符(delimiter)来切割字符串。

这看起来好像也没啥,不就是用正则匹配到定界符,然后切割嘛,很好理解。实际上,没有那么简单,这里边有坑(面试官可以拿去当面试题啦!)

正则表达式做分割符的坑

  • 正则表达式不使用括号

    在这种场景下,正则表达式做定界符,其实和用字符串做定界符是没有啥区别的。举个例子:

// 使用正则表达式
const str = '1a1b1c';

console.log(str.split(/\d/));
// Array ["", "a", "b", "c"]

console.log(str.split((1)));
// Array ["", "a", "b", "c"]
  • 正则表达式使用括号

    在这种场景下,正则表达式的括号内匹配到的内容,会作为一个元素包含在输出的数组内。再举个例子:

const str = '1a1b1c';
console.log(str.split((/(\d)/)));
// Array ["", "1", "a", "1", "b", "1", "c"]

注意,这里和上边的差距就在于正则多了个括号。MDN上是这么说明的:

Splitting with a RegExp to include parts of the separator in the result

If separator is a regular expression that contains capturing parentheses (), matched results are included in the array.

上边的内容意思是:给正则表达式加上捕获括号(capturing parentheses),就可以把匹配结果作为定界符,包含于输出数组中。

这就提示我们:给正则中想要作为定界符的那一部分加上括号,就可以定制我们的定界符。再举个例子:

  const reg = /<%=([\s\S]+?)%>/g;
  const str = '# <%= utils.classify(name) %>';
  console.log(str.split(reg));
  // Array ["# ", " utils.classify(name) ", ""]

在这里,我们可以看到,其实匹配到的内容是 <%= utils.classify(name) %> ,但是括号内只包含了[\s\S]+?,所以,只有 utils.classify(name) 作为定界符,被包含到了数组中,而正则匹配到的其他内容(<%=和%>),都被忽略了!

  • 正则表达式使用分组+括号

先举个例子:

  const reg = /<%-([\s\S]+?)%>|<%#([\s\S]+?)%>|<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g;
  const str = '# <%= utils.classify(name) %>';
  console.log(str.split(reg));
  // Array ["# ", undefined, undefined, " utils.classify(name) ", undefined, ""]

这是 @angular/cli 源码中的一部分,作用是匹配模板字符串中的特定语法(escape、注释、插补语法(interpolation)、语句)。细心的同学可能发现了:结果里边有一些 undefined,从顺序和数量来看,和正则中的分组是一一对应的。没错,当你使用分组的正则表达式,并且给分组加上捕获括号,那么当括号内的内容没有匹配到任何字符串时,undefined 就会作为定界符包含于数组中。当然,当我们把某一个分组的捕获括号去掉,对应的 undefined 就不会包含到数组里边。

那么:这种用法到底有什么用呢?

其实例子已经说明了:当你的字符串包含了特定的语法(类似 ejs 中的 <%、<%=、<%-、%>),同时你还希望通过有序的正则分组,知道切割出来的各个元素分别是什么语义(普通字符、escape、注释、interpolation、语句)?。那你就可以用这个方法进行切割,切割出来的结果一定是(分组数 + 1)的整数倍!这样,你就可以准确地知道每一个元素的语义,然后从容地转译你的模板。

好啦,split 函数就介绍到这里,对文中的不足和错误,欢迎批评指正,谢谢!

未经允许,请勿转载!