JavaScript 中正則匹配時結果不一致的問題

劉哇勇發表於2021-05-11

建立示例專案

考察如下場景,我們有個輸入框元件,輸入時同時進行校驗。

interface IInputProps {
  label: string;
}

function Input({ label }: IInputProps) {
  const [err, setErr] = useState<string | undefined>();

  return (
    <section>
      {label}<input
        type="text"
        onChange={(event) => {
          setErr(rulePassword(event.currentTarget.value));
        }}
      />
      <p>validate result:{err}</p>
    </section>
  );
}
 

進行校驗的邏輯使用了正則來測試:

const passwrodReg = new RegExp(
  // eslint-disable-next-line no-useless-escape
  /(?!^(\d+|[a-zA-Z]+|[_\+\-=!@#\$%\^\*\(\)]+)$)^[\w_\+\-=!@#\$%\^\*\(\)]{8,64}$/,
  "gm"
);

export const rulePassword = (value: string) => {
  const result = passwrodReg.test(value);
  console.log(`input:${value} result:${result}`);
  return result ? "✅" : "❌";
};
 

通常,如果是密碼輸入框,很自然地我們會放置兩個這樣的輸入框以讓使用者確保密碼的一致性:

function App() {
  return (
    <div className="App">
      <Input label="密碼" />
      <Input label="確認密碼" />
    </div>
  );
}
 

對於相同的輸入正則測試結果出現偏差

到此,示例寫完了,執行後發現個詭譎的問題,如下圖 GIF 中所展示:

validate resut

  • 當我們在第一個輸入框輸入合法值時,顯示校驗結果為通過,這符合預期
  • 當我們在第二個輸入框輸入相同的合法值時,居然顯示校驗未通過
  • 進一步,當刪除後再次輸入時,又展示校驗通過

同時,從控制檯列印的日誌也可重現上面的現象:

input:test123123 result:true
input:test123123 result:false
input: result:false
input:test123123 result:true
 

即,對於同樣的輸入 test123123,正則測試的結果居然會有偏差。

修正

當我們對校驗部分的邏輯做如下變更後這個問題得以解決。

- const passwrodReg = new RegExp(
-   // eslint-disable-next-line no-useless-escape
-   /(?!^(\d+|[a-zA-Z]+|[_\+\-=!@#\$%\^\*\(\)]+)$)^[\w_\+\-=!@#\$%\^\*\(\)]{8,64}$/,
-   "gm"
- );

export const rulePassword = (value: string) => {
+  const passwrodReg = new RegExp(
+    // eslint-disable-next-line no-useless-escape
+    /(?!^(\d+|[a-zA-Z]+|[_\+\-=!@#\$%\^\*\(\)]+)$)^[\w_\+\-=!@#\$%\^\*\(\)]{8,64}$/,
+    "gm"
+  );
  const result = passwrodReg.test(value);
  console.log(`input:${value} result:${result}`);
  return result ? "✅" : "❌";
};
 


validate resut

所以,一定是 RegExp 快取了什麼東西,上一次的匹配結果影響了下一次。

原因

通過檢視 MDN 文件發現,RegExp 通過 test() 匹配成功時,會記錄當前的位置資訊然後儲存到 RegExplastIndex,每成功匹配一次則更新一次該欄位。

並且,

Note: As long as test() returns true, lastIndex will not reset—even when testing a different string!

當配合 g 進行全域性匹配時,lastIndex 是不會重置的,即使是在匹配一個全新的字串時。

這就解釋了為什麼對於相同的輸入,第一次匹配成功後,後面則失敗了。

而當我們每次匹配都重新呼叫 RegExp 構造器生成正則時,就不會有這個問題了。

還有種解決方式是去掉 g 標識,每次匹配也不會複用之前的 lastIndex

相關資源

The text was updated successfully, but these errors were encountered:

相關文章