ในบทที่แล้วแนะนำเมธอด .indexOf
ซึ่งใช้ในการค้นหาข้อความที่ต้องการภายในสายอักขระไป
แต่ว่าหากต้องการค้นหาข้อความที่ไม่ใช่แค่แบบเดียวตายตัว
แต่เป็นกลุ่มคำบางอย่างที่มีเงื่อนไขซับซ้อน กรณีแบบนี้นิยมใช้เทคนิคที่เรียกว่า
เรกูลาร์เอ็กซ์เพรชชัน (regular expression) หรือเรียกย่อๆว่า
regex หรือ regexp
ในที่นี้ต่อจากตรงนี้จะขอเรียกสั้นๆว่า regex
ในหน้านี้จะเน้นแค่เรื่องวิธีการใช้ regex ภายในจาวาสคริปต์ ส่วนเรื่องที่ว่า
regex คืออะไร มีรูปแบบการเขียนอย่างไรสามารถดูได้ใน
https://phyblas.hinaboshi.com/20190709
regex เป็นเรื่องที่ซับซ้อน และใช้เวลาศึกษานาน
หากใครไม่ได้จำเป็นต้องใช้สามารถอ่านข้ามบทนี้ไปได้
ค้นหาคำที่เข้าข่าย
สายอักขระมีเมธอด search ใช้สำหรับค้นหาคำที่ต้องการโดยใช้ regex
เมธอดนี้จะบอกตำแหน่งของตัวแรกที่พบว่าเข้ากับรูปแบบ regex ที่ต้องการค้นหา
วิธีใช้
สายอักขระ.search(/รูปแบบที่ต้องการค้น/)
รูปแบบ regex ที่ต้องการค้นนั้นจะล้อมด้วยสแลช / / แบบนี้
ต่างจากสายอักขระที่จะล้อมรอบด้วยเครื่องหมายคำพูด " "
โครงสร้างที่ล้อมด้วยสแลช / / นั้นเป็นวิธีการสร้างออบเจ็กต์สำหรับ regex ขึ้นมา
เป็นออบเจ็กต์ชนิดหนึ่งที่ทำหน้าที่เก็บรูปแบบของ regex เอาไว้ใช้
ตัวอย่าง หาคำที่เป็นสระอา
var str = "เมื่อยามรักน้ำต้มผักก็ว่าหวาน";
alert(str.search(/[ก-ฮ]า[ก-ฮ]?/)); // ได้ 5
แบบนี้ จะตรงกับคำว่า "ยาม" ซึ่งอยู่ตำแหน่งที่ 5 (อักษรตัวที่หก)
เลยได้คำตอบเป็นเลข 5
ออบเจ็กต์ regex นั้นก็เป็นออบเจ็กต์ชนิดหนึ่ง
สามารถเก็บใส่ตัวแปรไว้ก่อนเพื่อนำมาใช้อีกทีได้
var reg = /.า.?/;
alert(str.search(reg));
หากหาไม่เจอจะได้ -1
alert("เก้า".search(/\d/)); // ได้ -1
ที่จริงแล้ว .search จะใส่สายอักขระธรรมดา ไม่ต้องเป็น regex ก็แต่
แต่แบบนี้ก็จะไม่ต่างอะไรกับใช้ .indexOf
alert("123".search('1')); // ได้ 0
แต่จริงๆก็สามารถเขียนรูปแบบ regex ลงในสายอักขระธรรมดาได้
เพียงแต่มีข้อควรระวังก็คือ หากมี \ อยู่จะต้องใส่เป็น ๒ ตัว
เพราะสายอักขระธรรมดาจะตีความ \ เป็นตัวเอสเคป
alert("123".search("\d")); // ได้ -1 (หาไม่เจอ)
alert("123".search("\\d")); // ได้ 0
การค้นด้วย match
search นั้นแค่หาตำแหน่งของคำที่ต้องการค้นเท่านั้น
หากต้องการจะได้คำที่เจอด้วยใช้ .match วิธีการใช้จะเหมือนกัน
แต่ผลที่ได้จะออกมาเป็นคำที่ต้องการค้น
ตัวอย่าง
var str = "แมว 13 ตัว หนู 101 ตัว";
var mat = str.match(/\d+/);
alert(mat); // ได้ 13
ผลที่ได้จากการ match นั้นแม้จะแสดงผลเหมือนเป็นสายอักขระเวลาดูค่าด้วย alert
แต่จริงๆไม่ใช่แค่สายอักขระ แต่จริงๆแล้วเป็น object
มีพรอเพอร์ตีเพิ่มเติมที่สำคัญคือ index ซึ่งเป็นตัวบอกตำแหน่ง
alert(typeof mat); // ได้ object
alert(mat.index); // ได้ 4
ดังนั้นเมื่อใช้ match
จะได้ทั้งข้อความที่ต้องการหาและยังได้ตำแหน่งของมันมาด้วยในเวลาเดียวกัน
การค้นหาทีละหลายตัว
ปกติแล้วเวลาค้นด้วย match จะเอาเฉพาะผลตัวแรกที่หาเจอ
แต่หากอยากให้หาหลายตัวเอาทุกตัวที่มีอยู่ให้ใส่ตัวเลือกเสริม g ไว้ข้างหลัง / /
กลายเป็น / /g แบบนี้ผลที่ได้จะกลับมาเป็นแถวลำดับของค่าที่หาเจอ
ตัวอย่าง
var str = "121~471#33*8657";
alert(str.match(/\d+/g)); // ได้ 121,471,33,8657
เพียงแต่ว่าเมื่อใช้ g แบบนี้แล้วผลที่ได้จะเป็นสายอักขระธรรมดา จะไม่สามารถใช้
.index ต่อท้ายผลลัพธ์เพื่อหาตำแหน่งได้
การทำให้ไม่แยกตัวพิมพ์เล็กพิมพ์ใหญ่
หากต้องการให้เวลาค้นหาไม่มีการแยกความต่างระหว่างตัวพิมพ์เล็กและพิมพ์ใหญ่ให้เติมตัวเลือกเสริม
i
ในขณะเดียวกันก็สามารถใส่ g ไปด้วยได้ ในกรณีแบบนี้ก็เขียนต่อกันไปได้เลย
ตัวอย่าง
var str = "aAáÁậẬ";
alert(str.match(/[aáậ]/g)); // ได้ a,á,ậ
alert(str.match(/[aáậ]/gi)); // ได้ a,A,á,Á,ậ,Ậ
การให้ถือว่าขึ้นบรรทัดใหม่เป็นจุดเริ่มต้นของคำ
ปกติแล้วถ้าใช้ ^ หรือ $
เพื่อกำหนดให้เฉพาะตัวที่อยู่ต้นหรือปลายเท่านั้นที่จะเข้าข่ายได้ แบบนี้ ^
จะพิจารณาเฉพาะตำแหน่งแรกสุดของสายอักขระ และ $ จะดูแค่ตำแหน่งสุดท้าย
แต่หากต้องการให้การขึ้นบรรทัดใหม่ทั้งหมดถือว่าการเริ่มคำใหม่
สามารถทำได้โดยใส่ตัวเลือกเสริม m
ลองดูความต่างระหว่างใส่ m กับไม่ใส่
var str = "กาก\nกาบ\nกาด";
alert(str.match(/[กบด]$/g)); // ได้ ก,บ,ด
alert(str.match(/[กบด]$/gm)); // ได้ ด
การแทนที่คำที่ต้องการด้วยอีกคำ
ในบทที่แล้วได้แนะนำเมธอด replace ไป แต่เมธอดนี้ถ้าใช้กับ regex
ก็จะทำอะไรได้มากขึ้น
วิธีการใช้ replace คู่กับ regex
สายอักขระ.replace(/รูปแบบ regex/, สิ่งที่ต้องการแทน)
ตัวอย่าง
var str = "takara";
alert(str.replace(/[ae]/g, "o")); // ได้ tokoro
ตรงส่วนที่ต้องการแทนนั้นสามารถใช้ฟังก์ชันได้
โดยฟังก์ชันนั้นจะรับพารามิเตอร์ตัวแรกเป็นข้อความที่เข้าข่ายทั้งหมด
ส่วนตัวถัดไปจะแทนตัวที่จัดกลุ่มไว้ด้วยวงเล็บ ตามลำดับ
ตัวอย่าง
var f = function(m, a, b) {
if (b == "า") return "โ" + a;
else return "โ" + a + b;
};
var str = "ทาการะ";
str = str.replace(/([ก-ฮ])([าะ])/g, f);
alert(str); // ได้ โทโกโระ
การแบ่งสายอักขระโดยใช้คำที่ต้องการเป็นตัวแบ่ง
เมธอด .split ใช้ในการแยกสายอักขระโดยใช้คำที่กำหนดเป็นตัวแบ่ง
การกำหนดคำที่ใช้แบ่งนั้นนอกจากจะใช้ตัวอักษรหรือข้อความใดๆตายตัวแล้ว
เราสามารถใช้ regex เพื่อกำหนดให้คำที่ใช้แบ่งเป็นอะไรที่ยืดหยุ่นขึ้น
var str = "ก|ข^ค-ง";
alert(str.split(/[|^-]/)); // ได้ ก,ข,ค,ง
การสร้างออบเจ็กต์ regex
การสร้างออบเจ็กต์สำหรับ regex นั้น นอกจากใช้ล้อมด้วยสแลช / /
แล้วยังอาจสร้างขึ้นโดยใช้คอนสตรักเตอร์ RegExp
รูปแบบการสร้าง
var ตัวแปร = new RegExp(รูปแบบการค้น, ตัวเลือกเสริม);
ตัวอย่าง
var reg = new RegExp(".a+", "gi");
จะมีความหมายเหมือนกับ
var reg = /.a+/gi;
วิธีอาจดูเหมือนจะยาวขึ้น แต่จะสะดวกที่จะใช้เมื่อเรามีสายอักขระที่เขียนรูปแบบ
regex อยู่แล้วต้องการแปลงเป็นออบเจ็กต์ regex
var strreg = "\\d\\d?"
var reg = new RegExp(strreg);
แต่การสร้างสายอักขระที่เขียน regex นั้นต้องอย่าลืมใส่แทน \ ด้วย \\
ไม่เช่นนั้นจะเกิดข้อผิดพลาดได้
ออบเจ็กต์ regex นั้นมีพรอเพอร์ตีดังนี้
source |
ข้อความแสดงรูปแบบ regex ที่เราใส่ไป |
global |
หาหลายตัวหรือไม่ (ใส่ g หรือไม่) |
multiline |
ให้แต่ละบรรทัดเป็นจุดเริ่มคำหรือไม่ (ใส่ m หรือไม่) |
ignoreCase |
ไม่แยกตัวพิมพ์เล็กพิมพ์ใหญ่หรือไม่ (ใส่ i หรือไม่) |
lastIndex |
ดัชนีสุดท้ายเมื่อใช้ .exec (หัวข้อถัดไป) |
ตัวอย่าง
var reg = /\d+/gm;
alert(reg.source); // ได้ \d+
alert(reg.global); // ได้ true
alert(reg.multiline); // ได้ true
alert(reg.ignoreCase); // ได้ false
alert(reg.lastIndex); // ได้ 0
เมธอด exec
ออบเจ็กต์ regex นั้นมีเมธอด exec ซึ่งทำงานคล้ายกับเมธอด search ในสายอักขระ
เพียงแต่ต่างกันตรงที่เป็นเมธอดที่ใช้กับออบเจ็กต์ regex
โดยมีสายอักขระเป็นอาร์กิวเมนต์
นอกจากนี้ยังมีความแตกต่างอีกอย่าง คือกรณีที่ใช้กับตัวเลือกเสริม g
เพื่อให้ค้นได้หลายตัว
เมื่อใส่ตัวเลือกเสริม g ไป ถ้าใช้ .search
จะได้ผลลัพธ์ออกมาหลายตัวแต่ว่าแต่ละตัวจะเป็นสายอักขระธรรมดาที่ไม่มีข้อมูลตำแหน่ง
แต่ถ้าใช้ .exec จะหาแค่ตัวเดียวก่อนแล้วจะมีการบันทึกตำแหน่งที่หาไปถึงไว้
จะหาตัวต่อไปก็ต้อง .exec ซ้ำอีกรอบ แล้วการหาก็จะเริ่มจากตำแหน่งนั้น
นอกจากนี้การหาในแต่ละรอบจะได้ออบเจ็กต์ผลการค้นซึ่งดู .index ได้หมด
ดังนั้นการใช้จึงยืดหยุ่นกว่า .search
ตัวอย่าง
var reg = /[^aiueo]?[aiueo]/g;
var str = "takaoka";
var mat = reg.exec(str);
alert([mat, mat.index, reg.lastIndex]); // ได้ ta,0,2
mat = reg.exec(str);
alert([mat, mat.index, reg.lastIndex]); // ได้ ka,2,4
mat = reg.exec(str);
alert([mat, mat.index, reg.lastIndex]); // ได้ o,4,5
สามารถใช้กับ while ได้
var reg = /[^aiueo]?[aiueo]/g;
var mat;
var str = "tapioka";
var s = "";
while ((mat = reg.exec(str)) != null) {
s += "เจอ " + mat + " ที่ตำแหน่ง " + mat.index + "\n";
}
alert(s);
ผลลัพธ์
เจอ ta ที่ตำแหน่ง 0
เจอ pi ที่ตำแหน่ง 2
เจอ o ที่ตำแหน่ง 4
เจอ ka ที่ตำแหน่ง 5
โค้ดอาจเข้าใจยากสักหน่อย ในที่นี้
mat = reg.exec(str)
คือการที่ให้ในแต่ละรอบป้อนค่าให้ตัวแปร mat
ส่วนที่เติม != null
ไว้ข้างหลังเป็นการให้เปรียบเทียบไปด้วยว่าค่าในแต่ละรอบนั้นยังมีอยู่หรือเปล่า
หาก exec หาไม่เจอแล้วก็จะได้ค่า null ออกมา จึงหลุดออกจากวังวน while ไปได้
แต่ถ้ายังมีค่าก็จะถูกแทนเข้าตัวแปร mat แล้วก็วนซ้ำต่อไป
หากรู้สึกว่าการเอาค่าไปใส่ในวงเล็บหลัง while ทำให้ดูยาก สับสนง่าย
อาจแก้เป็นโครงสร้างที่ใช้ break แบบนี้แทนได้ ผลเหมือนกัน
while (true) {
mat = reg.exec(str);
if (mat == null) break;
s += "เจอ " + mat + " ที่ตำแหน่ง " + mat.index + "\n";
}
เมธอด test
ออบเจ็กต์ regex ยังมีเมธอด test เอาไว้หาว่าข้อความมีส่วนที่เข้ารูปแบบใน regex
นั้นอยู่หรือไม่
เมธอดนี้ทำได้แค่ให้ค่า true หรือ false
ถ้ามีส่วนไหนของสายอักขระที่เข้าข่ายสักที่ก็ได้ true ถ้าไม่มีเลยก็ได้ false
ตัวอย่าง
alert(/\d/.test("12")); // ได้ true
alert(/\d/.test("ab")); // ได้ false
alert(/\d\d/.test("1a")); // ได้ false