JavaScript
String Methods
indexOf / lastIndexOf
เรียนรู้ indexOf() และ lastIndexOf() สำหรับค้นหาตำแหน่ง substring ใน string — indexOf() ค้นจากซ้ายไปขวาคืนค่า index แรกที่เจอ, lastIndexOf() ค้นจากขวาไปซ้ายคืนค่า index สุดท้ายที่เจอ, ทั้งคู่คืน -1 เมื่อไม่พบ พร้อมเรียนรู้ position parameter ที่มีพฤติกรรมต่างกันในแต่ละ method การเปรียบเทียบกับ includes() และการนำไปใช้จริงในการค้นหาทุก occurrence ตรวจสอบรูปแบบข้อมูล และหาตำแหน่งตัวคั่น
indexOf() — ค้นหาตำแหน่ง substring จากซ้ายไปขวา
indexOf() คือ method สำหรับค้นหาตำแหน่ง (index) ของ substring ใน string — คล้ายกับ indexOf() ของ array ที่เราเรียนไปแล้ว แต่ใช้กับ string syntax: str.indexOf(searchString, position?) indexOf() จะคืนค่าเป็น number: • ถ้าพบ substring — คืนค่า index แรกที่เจอ (เริ่มนับจาก 0) • ถ้าไม่พบ — คืนค่า -1 • ค้นหาจากซ้ายไปขวา (index 0 → index สุดท้าย) • case-sensitive — ตัวพิมพ์เล็ก-ใหญ่ต้องตรงกัน ความแตกต่างหลักจาก includes(): includes() คืนค่า boolean (true/false) — ใช้เมื่ออยากรู้ว่า "มีหรือไม่มี" ส่วน indexOf() คืนค่า number (ตำแหน่ง) — ใช้เมื่ออยากรู้ว่า "อยู่ที่ไหน"
let lang = "JavaScript";
console.log(lang.indexOf("Script")); // 4 — "Script" เริ่มที่ index 4
console.log(lang.indexOf("Java")); // 0 — "Java" เริ่มที่ index 0
console.log(lang.indexOf("a")); // 1 — "a" ตัวแรกอยู่ที่ index 1
console.log(lang.indexOf("Python")); // -1 — ไม่พบ "Python"
console.log(lang.indexOf("script")); // -1 — case-sensitive! "s" ตัวเล็ก ≠ "S" ตัวใหญ่
console.log(lang.indexOf("")); // 0 — string ว่าง "อยู่" ที่ index 0 เสมอ
console.log(lang); // "JavaScript" — ตัวเดิมไม่เปลี่ยน- indexOf() คืนค่า number — ตำแหน่ง index แรกที่เจอ (0, 1, 2, ...) หรือ -1 ถ้าไม่เจอ
- เหมือน indexOf() ของ array ที่เคยเรียน — หลักการเดียวกันแต่ค้นหา substring ใน string แทน element ใน array
- case-sensitive — "Script" กับ "script" ถือว่าไม่เหมือนกัน
- empty string ("") → 0 เสมอ — เพราะ "" อยู่ที่ index 0 ในทางเทคนิค
- รับ parameter ได้ 2 ตัว: searchString (จำเป็น) และ position (ไม่จำเป็น, default = 0)
position parameter — ระบุตำแหน่งเริ่มค้นหา
indexOf() รับ parameter ตัวที่สองคือ position — กำหนดตำแหน่งเริ่มค้นหา (default = 0 คือเริ่มจากตัวแรก) เหมือน position ใน includes() ที่เราเรียนไป syntax: str.indexOf(searchString, position) กฎสำคัญ: • ถ้าไม่ระบุ position — เริ่มค้นหาจาก index 0 • ถ้า position >= str.length — คืนค่า -1 เสมอ เพราะไม่มีอะไรให้ค้นหาตั้งแต่ตำแหน่งนั้น • ถ้า position < 0 — จะถูกปรับเป็น 0 (เริ่มจากต้น string) • ประโยชน์หลัก: ใช้หาครั้งที่ 2, 3, ... ของ substring เดิม — โดยเริ่มค้นหาจาก index ถัดจากที่เจอครั้งก่อน
ใช้ position เพื่อหาครั้งที่ 2 และค้นหาตั้งแต่ตำแหน่งที่กำหนด
let text = "Hello Hello";
// ไม่ระบุ position — เจอ "Hello" ครั้งแรกที่ index 0
console.log(text.indexOf("Hello")); // 0
// position = 1 — ข้าม "Hello" ตัวแรก เริ่มค้นจาก index 1
console.log(text.indexOf("Hello", 1)); // 6 — เจอ "Hello" ตัวที่สอง
// position >= length — -1 เสมอ
console.log(text.indexOf("Hello", 100)); // -1
// position ติดลบ — ปรับเป็น 0
console.log(text.indexOf("Hello", -5)); // 0 — เริ่มจากต้น
// --- ใช้ position หาทุก occurrence ---
let word = "banana";
let pos = word.indexOf("a"); // 1 — "a" ตัวแรก
let pos2 = word.indexOf("a", 2); // 3 — "a" ตัวที่สอง (เริ่มจาก index 2)
let pos3 = word.indexOf("a", 4); // 5 — "a" ตัวที่สาม (เริ่มจาก index 4)
console.log(pos, pos2, pos3); // 1 3 5- position = 0: เริ่มค้นหาจากตัวแรกของ string (default)
- position >= str.length: คืนค่า -1 เสมอ — ไม่มีอะไรให้ค้นหา
- position < 0: ถูกปรับเป็น 0 — เริ่มจากต้น string
- ใช้ position หา occurrence ที่ 2, 3, ...: เริ่มค้นหาจาก index ถัดจากที่เจอครั้งก่อน + 1
- หลักการเดียวกับ position ใน includes() ที่เรียนไปแล้ว
lastIndexOf() — ค้นหาตำแหน่ง substring จากขวาไปซ้าย
lastIndexOf() คือ method ค้นหาตำแหน่งของ substring เช่นเดียวกับ indexOf() แต่ค้นจากขวาไปซ้าย — คืนค่า index ของ occurrence สุดท้ายที่พบ syntax: str.lastIndexOf(searchString, position?) พฤติกรรมสำคัญ: • ค้นหาจากขวาไปซ้าย — คืนค่า index ของการพบครั้งสุดท้าย • index ที่คืนเป็นตำแหน่งปกติ (นับจากซ้าย) — ไม่ใช่การนับจากขวา • ถ้าไม่พบ — คืนค่า -1 • case-sensitive — ตัวพิมพ์เล็ก-ใหญ่ต้องตรงกัน • string ว่าง ("") — คืนค่า str.length (ความยาวของ string) เปรียบเทียบ: ถ้า indexOf() คือ "เจอที่ไหนเป็นครั้งแรก" — lastIndexOf() คือ "เจอที่ไหนเป็นครั้งสุดท้าย"
let phrase = "Hello World Hello";
// lastIndexOf — หา "Hello" ครั้งสุดท้าย
console.log(phrase.lastIndexOf("Hello")); // 12 — "Hello" ตัวที่สอง (ครั้งสุดท้าย)
// indexOf — หา "Hello" ครั้งแรก
console.log(phrase.indexOf("Hello")); // 0 — "Hello" ตัวแรก
// ถ้ามีแค่ครั้งเดียว — indexOf และ lastIndexOf ให้ค่าเท่ากัน
console.log(phrase.lastIndexOf("World")); // 6
console.log(phrase.indexOf("World")); // 6
// --- ตัวอย่างเพิ่มเติม ---
let word = "banana";
console.log(word.lastIndexOf("a")); // 5 — "a" ตัวสุดท้าย
console.log(word.indexOf("a")); // 1 — "a" ตัวแรก
// ไม่พบ → -1
console.log(word.lastIndexOf("x")); // -1
// empty string → str.length
console.log(word.lastIndexOf("")); // 6 — word.length = 6
console.log(phrase); // "Hello World Hello" — immutable- lastIndexOf() คืนค่า index ของ occurrence สุดท้าย — ค้นจากขวาไปซ้าย
- index ที่คืนเป็นตำแหน่งนับจากซ้าย (ปกติ) — ไม่ใช่การนับถอยหลังจากขวา
- ถ้า substring มีเพียงครั้งเดียว — indexOf() และ lastIndexOf() จะคืนค่าเท่ากัน
- empty string ("") → str.length — ต่างจาก indexOf() ที่คืน 0
- case-sensitive เหมือน indexOf() — ตัวพิมพ์เล็ก-ใหญ่ต้องตรงกัน
- string เดิมไม่เปลี่ยน — immutable
lastIndexOf() position parameter — จำกัดขอบเขตการค้นหา
lastIndexOf() ก็รับ position parameter ได้เช่นกัน — แต่ทำงานต่างจาก indexOf() syntax: str.lastIndexOf(searchString, position) ใน lastIndexOf() — position ทำหน้าที่เป็นขอบเขตบน (upper bound): • ค้นหาจากตำแหน่ง position ถอยกลับมาทางซ้าย • หมายความว่า จะค้นหาเฉพาะตำแหน่งที่ ≤ position เท่านั้น • ถ้าไม่ระบุ position — default คือ str.length (ค้นหาทั้ง string) • ถ้า position >= str.length — ค้นหาทั้ง string (เหมือนไม่ระบุ) • ถ้า position < 0 — คืนค่า -1 (ไม่มีอะไรให้ค้นหา) นี่คือความแตกต่างสำคัญจาก indexOf(): indexOf(position) เริ่มค้นจาก position ไปทางขวา แต่ lastIndexOf(position) เริ่มค้นจาก position ไปทางซ้าย
position ใน lastIndexOf จำกัดขอบเขตการค้นหา
let text = "one two one two one";
// ไม่ระบุ position — หาทั้ง string
console.log(text.lastIndexOf("one")); // 16 — "one" ตัวสุดท้าย
// position = 10 — ค้นหาเฉพาะ index 0 ถึง 10
console.log(text.lastIndexOf("one", 10)); // 8 — "one" ตัวกลาง (ภายในขอบเขต ≤ 10)
// position = 5 — ค้นหาเฉพาะ index 0 ถึง 5
console.log(text.lastIndexOf("one", 5)); // 0 — "one" ตัวแรก
// position = 0 — ค้นหาเฉพาะ index 0
console.log(text.lastIndexOf("one", 0)); // 0 — "one" อยู่ที่ index 0 พอดี
// position < 0 — -1 เสมอ
console.log(text.lastIndexOf("one", -1)); // -1
// --- lastIndexOf vs indexOf กับ position ---
let word = "Hello";
console.log(word.indexOf("l")); // 2 — "l" ตัวแรกจากซ้าย
console.log(word.lastIndexOf("l")); // 3 — "l" ตัวสุดท้ายจากซ้าย
console.log(word.indexOf("l", 2)); // 2 — เริ่มจาก index 2 ไปขวา
console.log(word.lastIndexOf("l", 2)); // 2 — เริ่มจาก index 2 ไปซ้าย- lastIndexOf(position): position เป็นขอบเขตบน — ค้นหาเฉพาะ index ≤ position
- indexOf(position): position เป็นจุดเริ่ม — ค้นหาจาก index ≥ position ไปทางขวา
- lastIndexOf() ไม่ระบุ position: default = str.length — ค้นหาทั้ง string
- lastIndexOf(position) เมื่อ position >= str.length: ค้นหาทั้ง string
- lastIndexOf(position) เมื่อ position < 0: คืน -1 เสมอ (ต่างจาก indexOf ที่ปรับเป็น 0)
- ใช้ lastIndexOf(position) เมื่อต้องการหาการเกิดครั้งก่อนหน้าตำแหน่งที่กำหนด — เช่น หาตัวคั่นก่อนตำแหน่งปัจจุบัน
เปรียบเทียบ indexOf / lastIndexOf / includes
ทั้งสาม method ใช้ค้นหา substring ใน string แต่มีจุดประสงค์และพฤติกรรมต่างกัน — เลือกใช้ให้เหมาะกับโจทย์:
| พฤติกรรม | indexOf() | lastIndexOf() | includes() |
|---|---|---|---|
| ค่าที่คืน | number (index หรือ -1) | number (index หรือ -1) | boolean (true/false) |
| ทิศทางการค้นหา | ซ้าย → ขวา | ขวา → ซ้าย | ซ้าย → ขวา |
| เจอครั้งแรก/สุดท้าย | ครั้งแรก | ครั้งสุดท้าย | ไม่ระบุ (แค่มีหรือไม่มี) |
| position parameter | จุดเริ่มค้น (ไปขวา) | ขอบเขตบน (ไปซ้าย) | จุดเริ่มค้น (ไปขวา) |
| empty string ("") | 0 | str.length | true |
| case-sensitive | ใช่ | ใช่ | ใช่ |
| ใช้เมื่อ... | อยากรู้ตำแหน่ง | อยากรู้ตำแหน่งสุดท้าย | อยากรู้ว่ามีหรือไม่มี |
let str = "Hello Hello";
// --- ตรวจสอบว่ามีหรือไม่มี ---
console.log(str.includes("Hello")); // true — แค่รู้ว่ามี
console.log(str.indexOf("Hello") !== -1); // true — รู้ว่ามี + รู้ตำแหน่ง
// --- หาตำแหน่ง ---
console.log(str.indexOf("Hello")); // 0 — เจอครั้งแรกที่ index 0
console.log(str.lastIndexOf("Hello")); // 6 — เจอครั้งสุดท้ายที่ index 6
// --- position ต่างกัน ---
console.log(str.indexOf("Hello", 2)); // 6 — ค้นจาก index 2 ไปขวา
console.log(str.lastIndexOf("Hello", 5)); // 0 — ค้นจาก index 5 ไปซ้าย
// --- ใช้ให้เหมาะ ---
// ถ้าอยากรู้แค่ "มี @ ไหม": ใช้ includes
console.log("a@b".includes("@")); // true
// ถ้าอยากรู้ "@ อยู่ index ไหน": ใช้ indexOf
console.log("a@b".indexOf("@")); // 1
// ถ้าอยากรู้ "." ตัวสุดท้ายอยู่ index ไหน: ใช้ lastIndexOf
console.log("a.b.c".lastIndexOf(".")); // 3การใช้งานจริง
indexOf() และ lastIndexOf() ใช้บ่อยในงานจริงเมื่อต้องการรู้ตำแหน่งของ substring — มาดูตัวอย่างการใช้งานที่พบบ่อย:
ก่อนจะมี includes() — โปรแกรมเมอร์ใช้ indexOf() !== -1 ในการตรวจสอบ
let email = "user@example.com";
// วิธีดั้งเดิม — ใช้ indexOf !== -1
if (email.indexOf("@") !== -1) {
console.log("มี @ ใน email");
}
// วิธีใหม่ — ใช้ includes (อ่านง่ายกว่า)
if (email.includes("@")) {
console.log("มี @ ใน email");
}
// เลือกใช้ให้เหมาะ:
// - ต้องการแค่รู้ว่ามีหรือไม่มี → includes() อ่านง่ายกว่า
// - ต้องการรู้ตำแหน่ง + ตรวจสอบว่ามีหรือไม่มี → indexOf() ทำทีเดียวจบใช้ while loop หาทุกตำแหน่งที่ substring ปรากฏ
let text = "The cat and the cat food";
let search = "cat";
let positions = [];
let pos = text.indexOf(search);
while (pos !== -1) {
positions.push(pos);
pos = text.indexOf(search, pos + 1); // เริ่มค้นถัดจากที่เจอ
}
console.log(positions); // [4, 16] — "cat" อยู่ที่ index 4 และ 16หา . ตัวสุดท้ายเพื่อแยกนามสกุลไฟล์
let filename = "document.backup.pdf";
let dotIndex = filename.lastIndexOf("."); // 16 — "." ตัวสุดท้าย
let hasExtension = dotIndex !== -1; // true — มีนามสกุลไฟล์
console.log(dotIndex); // 16
console.log(hasExtension); // true
// หมายเหตุ: ในบทถัดไปเราจะเรียน slice() และ substring()
// ซึ่งใช้คู่กับ indexOf/lastIndexOf เพื่อดึงส่วนของ string ออกมาตรวจสอบว่า string มีรูปแบบตามที่คาดหวังหรือไม่
// ตรวจสอบว่า email มี @ และมี . หลัง @
let email = "user@example.com";
let atPos = email.indexOf("@");
let lastDot = email.lastIndexOf(".");
// ต้องมี @ และมี . อยู่หลัง @
let valid = atPos !== -1 && lastDot > atPos;
console.log(valid); // true
// ทดสอบกับ email ที่ไม่ถูกต้อง
let badEmail = "userexample.com";
console.log(badEmail.indexOf("@") === -1); // true — ไม่มี @
console.log(badEmail.indexOf("@") !== -1 &&
badEmail.lastIndexOf(".") > badEmail.indexOf("@")); // false- ใช้ indexOf() !== -1 เพื่อตรวจสอบว่ามี substring หรือไม่ — pattern ดั้งเดิมก่อนมี includes()
- ใช้ indexOf() + while loop หาทุก occurrence — เริ่มค้นถัดจากตำแหน่งที่เจอครั้งก่อน + 1
- ใช้ lastIndexOf() หา . ตัวสุดท้ายสำหรับนามสกุลไฟล์ — จะใช้คู่กับ slice() ในบทถัดไป
- ใช้ indexOf + lastIndexOf ร่วมกันตรวจสอบรูปแบบข้อมูล — เช่น email, URL, path
ข้อผิดพลาดที่พบบ่อย
มือใหม่มักเจอข้อผิดพลาดเหล่านี้เมื่อใช้ indexOf() และ lastIndexOf():
- ลืมตรวจสอบ -1 — ใช้ indexOf() แล้วเอาผลลัพธ์ไปใช้ต่อโดยไม่ตรวจสอบว่ามี substring นั้นจริงหรือไม่ — ถ้าไม่เจอ (-1) อาจทำให้ logic พัง เช่น ใช้ slice(-1) หรือเข้าถึง array ด้วย indexOf ผลลัพธ์
- ใช้ indexOf() เมื่อต้องการแค่รู้ว่ามีหรือไม่มี — indexOf() !== -1 อ่านยากกว่า includes() โดยตรง — ถ้าต้องการแค่ boolean ให้ใช้ includes()
- ใช้ lastIndexOf(position) แล้วเข้าใจผิดว่าค้นหาจาก position ไปทางขวา — จริง ๆ แล้ว lastIndexOf ค้นหาจาก position ถอยกลับมาทางซ้าย — เป็นพฤติกรรมตรงข้ามกับ indexOf()
- string ว่าง ("") — indexOf("") → 0 เสมอ, lastIndexOf("") → str.length เสมอ — อาจทำให้เกิด bug หาก searchString มาจาก user input ที่เป็นค่าว่าง
- ลืมว่า case-sensitive — indexOf("A") กับ indexOf("a") ให้ผลต่างกัน — ใช้ toLowerCase() หรือ toUpperCase() ช่วยถ้าต้องการ case-insensitive
- ใช้ indexOf() กับสิ่งที่ใช้ === ตรวจไม่ได้ — indexOf() ใช้ Strict Equality (===) ในการเปรียบเทียบ — เช่น ใช้กับ NaN หรือ object จะทำงานไม่ตรงกับที่คิด (แต่ไม่ค่อยเป็นปัญหากับ string เพราะ string เปรียบเทียบด้วย === ได้ตรงตัว)
let text = "Hello World";
// ❌ ผิด: ใช้ indexOf แล้วไม่ตรวจสอบ -1
let idx = text.indexOf("xyz"); // -1
// text.slice(idx); // ❌ slice(-1) — ได้ผลลัพธ์ไม่ตรงที่คิด!
// ✅ ถูก: ตรวจสอบ -1 ก่อนใช้
let idx2 = text.indexOf("xyz");
if (idx2 !== -1) {
// ใช้ idx2 ได้อย่างปลอดภัย
}
// ❌ ผิด: ใช้ indexOf เมื่อต้องการแค่ boolean
if (text.indexOf("Hello") !== -1) { } // ยาวเกินจำเป็น
// ✅ ถูก: ใช้ includes เมื่อต้องการแค่ boolean
if (text.includes("Hello")) { } // อ่านง่ายกว่า
// ❌ ผิด: เข้าใจผิดว่า lastIndexOf(position) ค้นไปทางขวา
console.log(text.lastIndexOf("o", 2)); // -1 — ค้นจาก index 2 ไปซ้าย ไม่เจอ "o"
// ✅ ถูก: ใช้ indexOf ถ้าอยากค้นไปทางขวา
console.log(text.indexOf("o", 2)); // 4 — ค้นจาก index 2 ไปขวา เจอ "o"สรุป
- indexOf(searchString, position?) — ค้นหาตำแหน่ง substring จากซ้ายไปขวา คืนค่า index แรกที่เจอ หรือ -1 ถ้าไม่พบ
- lastIndexOf(searchString, position?) — ค้นหาตำแหน่ง substring จากขวาไปซ้าย คืนค่า index สุดท้ายที่เจอ หรือ -1 ถ้าไม่พบ
- position ใน indexOf: กำหนดตำแหน่งเริ่มค้นหา (default = 0) — ค้นหาจากตำแหน่งนั้นไปทางขวา
- position ใน lastIndexOf: กำหนดขอบเขตบน (default = str.length) — ค้นหาจากตำแหน่งนั้นกลับมาทางซ้าย (≤ position)
- indexOf() vs includes(): indexOf() คืนตำแหน่ง (ใช้เมื่ออยากรู้ "อยู่ที่ไหน") — includes() คืน boolean (ใช้เมื่ออยากรู้ "มีหรือไม่มี")
- indexOf() vs lastIndexOf(): indexOf() เจอครั้งแรก — lastIndexOf() เจอครั้งสุดท้าย
- ทั้งคู่เป็น case-sensitive — ตัวพิมพ์เล็ก-ใหญ่ต้องตรงกัน — ใช้ toLowerCase()/toUpperCase() ถ้าต้องการ case-insensitive
- string เดิมไม่เปลี่ยน (immutable) — ทั้ง indexOf() และ lastIndexOf() คืนค่า number ไม่ใช่ string ใหม่
- string ว่าง ("") — indexOf("") → 0, lastIndexOf("") → str.length
- ใช้ indexOf + position ใน while loop เพื่อหาทุก occurrence — pattern ที่ใช้บ่อยในงานจริง
- ใช้ lastIndexOf หาจุดสิ้นสุด (เช่น . ตัวสุดท้ายของชื่อไฟล์) — ใช้คู่กับ slice() ในบทถัดไป