JavaScript拆分字符串時產生空字符的緣由

圖片描述

問題描述

使用JavaScript的split方法拆分字符串時出現一些空字符串"",尤爲是當使用正則表達式做爲分隔符的時候。javascript

相關問題

javascript正則表達式對字符串分組時產生空字符串組?java

在上面這個問題中,題主使用正則表達式對字符串進行分割時產生了多個空字符串"",代碼以下:正則表達式

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

那麼,產生這些空字符串的緣由是什麼?segmentfault

問題分析

在Google上搜索了一番,發現相關的結果並很少,即使有,詳細解釋的也很少,大概的說了一下,而後就給出了一個ECMAScript規範的連接。看來要想知道真正的緣由,就只能硬着頭皮看規範了。數組

相關標準

那麼,接下來,按照國際慣例,先上ECMAScript的標準鎮樓。google

String.prototype.split (separator, limit)spa

這個章節詳細介紹了split方法的執行步驟,若是感興趣的話能夠一步一步的認真看完,我在這裏只把和產生空字符串相關的步驟拿出來解釋一下,不當之處,歡迎你們提出。prototype

相關步驟

摘取部分步驟:code

步驟截圖

整個過程當中最主要的步驟是第13步這個循環,而這個循環主要作的事情以下:javascript正則

  • 定義p, q的值,每一次循環開始的時候pq的值是相同的(該步驟在循環以外);
  • 調用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類型的變量。經過查文檔發現,該類型的變量有兩個屬性endIndexcapturesendIndex的值是字符串匹配的位置加上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是會被忽略的,這也算是一次額外的收穫。

相關文章
相關標籤/搜索