Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> JavaScript

JavaScript RegExp test() Method - ทดสอบการ Match ของ Regular Expression


RegExp.prototype.test() คืออะไร?

regex.test(string) ทดสอบว่ารูปแบบ (pattern) ตรงกับสตริงที่ให้มาหรือไม่ คืนค่า Boolean: true ถ้าตรง, false ถ้าไม่ตรง

  • เร็วกว่า match() หรือ exec() เพราะไม่ต้องสร้าง array ผลลัพธ์
  • เหมาะสำหรับ Validation, Condition checking
  • ถ้ามี flag g จะใช้ lastIndex (stateful) - ต้องระวัง!

ตัวอย่างโค้ดครบถ้วน

<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>RegExp test() Demo</title>
  <style>
    body { font-family: 'Segoe UI', sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }
    .panel { background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
    .code { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 4px; font-family: monospace; overflow-x: auto; }
    .output { background: #e8f5e9; padding: 15px; border-radius: 4px; font-family: monospace; white-space: pre-wrap; }
    .test-case { display: flex; gap: 10px; align-items: center; margin: 10px 0; flex-wrap: wrap; }
    .test-case input { flex: 1; min-width: 200px; padding: 8px; font-family: monospace; border: 1px solid #ddd; border-radius: 4px; }
    .test-case button { padding: 8px 16px; background: #7e32d4; color: white; border: none; border-radius: 4px; cursor: pointer; }
    .result-badge { padding: 4px 12px; border-radius: 20px; font-weight: bold; font-size: 14px; }
    .match { background: #c8e6c9; color: #2e7d32; }
    .no-match { background: #ffcdd2; color: #c62828; }
    table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    th, td { padding: 10px; border: 1px solid #ddd; text-align: left; }
    th { background: #7e32d4; color: white; }
  </style>
</head>
<body>
  <h1>RegExp.prototype.test() - ทดสอบ Pattern Matching</h1>
  
  <div class="panel">
    <h3>ทดสอบแบบโต้ตอบ</h3>
    <div class="test-case">
      <label>Pattern: <input type="text" id="patternInput" value="\d{3}-\d{2}-\d{4}" /></label>
      <label>Flags: <input type="text" id="flagsInput" value="" placeholder="g, i, m..." /></label>
    </div>
    <div class="test-case">
      <label>Test String: <input type="text" id="testInput" value="My SSN is 123-45-6789" /></label>
      <button onclick="runTest()">Test Match</button>
    </div>
    <div class="output" id="testOutput"></div>
  </div>
  
  <div class="panel">
    <h3>ตัวอย่าง Patterns มาตรฐาน</h3>
    <button onclick="demoCommonPatterns()">รันตัวอย่าง Patterns ทั่วไป</button>
    <div class="output" id="commonOutput"></div>
  </div>
  
  <div class="panel">
    <h3>⚠️ พัฒนาการ (Gotcha) กับ Flag 'g' (Global)</h3>
    <button onclick="demoGlobalFlag()">สาธิตปัญหา Global Flag</button>
    <div class="output" id="globalOutput"></div>
  </div>

  <script>
    const testOutput = document.getElementById('testOutput');
    const commonOutput = document.getElementById('commonOutput');
    const globalOutput = document.getElementById('globalOutput');
    
    function log(el, msg, isMatch = null) {
      const div = document.createElement('div');
      div.textContent = msg;
      if (isMatch === true) div.className = 'result-badge match';
      else if (isMatch === false) div.className = 'result-badge no-match';
      el.appendChild(div);
    }
    
    function runTest() {
      testOutput.innerHTML = '';
      const pattern = document.getElementById('patternInput').value;
      const flags = document.getElementById('flagsInput').value;
      const testStr = document.getElementById('testInput').value;
      
      try {
        const regex = new RegExp(pattern, flags);
        const result = regex.test(testStr);
        log(testOutput, `Regex: ${regex.toString()}`, result);
        log(testOutput, `Test string: "${testStr}"`);
        log(testOutput, `Result: ${result}`, result);
      } catch (e) {
        log(testOutput, `Error: ${e.message}`);
      }
    }
    
    function demoCommonPatterns() {
      commonOutput.innerHTML = '';
      const tests = [
        { pattern: /^\d{10}$/, test: '0812345678', desc: 'Thai Phone (10 digits)' },
        { pattern: /^[\w.-]+@[\w.-]+\.\w+$/, test: 'user@example.com', desc: 'Email (simple)' },
        { pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/, test: 'Pass1234', desc: 'Password: 8+ chars, upper, lower, digit' },
        { pattern: /^https?:\/\/.+/, test: 'https://example.com', desc: 'URL http/https' },
        { pattern: /^\d{4}-\d{2}-\d{2}$/, test: '2024-01-15', desc: 'Date YYYY-MM-DD' },
        { pattern: /^#[0-9A-Fa-f]{6}$/, test: '#FF5733', desc: 'Hex Color' },
        { pattern: /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, test: '192.168.1.1', desc: 'IPv4 Address' },
      ];
      
      tests.forEach(({ pattern, test, desc }) => {
        const result = pattern.test(test);
        log(commonOutput, `${desc}: ${pattern.toString()} → "${test}" = ${result}`, result);
      });
    }
    
    function demoGlobalFlag() {
      globalOutput.innerHTML = '';
      const regex = /foo/g; // Global flag!
      const str = 'foo bar foo baz';
      
      log(globalOutput, `Regex: ${regex.toString()} (has 'g' flag)`);
      log(globalOutput, `String: "${str}"`);
      log(globalOutput, `---`);
      
      // เรียก test() หลายครั้ง
      log(globalOutput, `1st test: ${regex.test(str)} (lastIndex: ${regex.lastIndex})`);
      log(globalOutput, `2nd test: ${regex.test(str)} (lastIndex: ${regex.lastIndex})`);
      log(globalOutput, `3rd test: ${regex.test(str)} (lastIndex: ${regex.lastIndex})`);
      log(globalOutput, `4th test: ${regex.test(str)} (lastIndex: ${regex.lastIndex})`);
      log(globalOutput, `---`);
      log(globalOutput, '⚠️ lastIndex ไม่ reset ระหว่างการเรียกต่าง string!');
      
      // แก้ไข: reset lastIndex หรือสร้าง regex ใหม่ทุกครั้ง
      log(globalOutput, '\n--- แก้ไข: ใช้ regex ใหม่ทุกครั้ง ---');
      const str2 = 'foo bar';
      log(globalOutput, `new RegExp('foo', 'g').test("${str2}") = ${new RegExp('foo', 'g').test(str2)}`);
      log(globalOutput, `new RegExp('foo', 'g').test("${str2}") = ${new RegExp('foo', 'g').test(str2)}`);
    }
  </script>
</body>
</html>

พฤติกรรมสำคัญ: Flag 'g' (Global) และ lastIndex

const regex = /foo/g;
const str = 'foo bar foo';

regex.test(str); // true  - lastIndex = 3
regex.test(str); // true  - lastIndex = 12 (เจอ "foo" ตัวที่ 2)
regex.test(str); // false - lastIndex = 0 (reset หลังไม่เจอ)
regex.test(str); // true  - เริ่มใหม่จาก 0

⚠️ ปัญหา: ใช้ regex เดียวกับ 'g' flag ทดสอบ string คนละตัว

const regex = /cat/g;

regex.test('cat');    // true  (lastIndex = 3)
regex.test('dog');    // false (lastIndex = 0 - reset เพราะไม่เจอ)
regex.test('cat');    // true  (OK)

regex.test('cat');    // true  (lastIndex = 3)
regex.test('cat');    // false! (lastIndex = 3, เริ่มค้นจาก index 3 ซึ่งไม่มี 'cat')

✅ แก้ไข: ไม่ใช้ 'g' flag สำหรับ test() หรือ Reset lastIndex

// วิธี 1: ไม่ใช้ g flag (แนะนำสำหรับ test)
const regex1 = /cat/; // ไม่มี g
regex1.test('cat'); // true
regex1.test('cat'); // true (เสมอ)

// วิธี 2: Reset lastIndex ก่อนใช้
const regex2 = /cat/g;
function testGlobal(regex, str) {
  regex.lastIndex = 0;
  return regex.test(str);
}

// วิธี 3: สร้าง regex ใหม่ทุกครั้ง (ถ้า pattern ไม่คงที่)
function createTester(pattern, flags = '') {
  return (str) => new RegExp(pattern, flags.replace('g', '')).test(str);
}

การประยุกต์ใช้จริง: Form Validation

const validators = {
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  phone: /^0\d{9}$/,
  password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$/,
  username: /^[a-zA-Z0-9_]{3,20}$/,
  thaiId: /^\d{13}$/,
  zipCode: /^\d{5}$/
};

function validateField(fieldName, value) {
  const regex = validators[fieldName];
  if (!regex) return { valid: true }; // ไม่มี rule
  
  const isValid = regex.test(value);
  return {
    valid: isValid,
    message: isValid ? '' : `รูปแบบ ${fieldName} ไม่ถูกต้อง`
  };
}

// ใช้งาน
const emailResult = validateField('email', 'user@example.com');
// { valid: true, message: '' }

const phoneResult = validateField('phone', '0812345678');
// { valid: true, message: '' }

Performance: test() vs match() vs exec()

Methodคืนค่าใช้เมื่อความเร็ว
regex.test(str)booleanแค่ต้องการรู้ว่าตรงหรือไม่⚡ เร็วที่สุด
str.match(regex)Array / nullต้องการข้อมูล match (capture groups)ปานกลาง
regex.exec(str)Array / nullต้องการข้อมูลละเอียด + ใช้กับ 'g' flag loopปานกลาง
str.search(regex)index / -1ต้องการตำแหน่งแรกที่เจอเร็ว