חקרו התאמת תבניות מתקדמת ב-JavaScript באמצעות ביטויים רגולריים. למדו על תחביר Regex, יישומים מעשיים וטכניקות אופטימיזציה לקוד יעיל וחזק.
התאמת תבניות ב-JavaScript עם ביטויים רגולריים: מדריך מקיף
ביטויים רגולריים (regex) הם כלי רב עוצמה להתאמת תבניות ולמניפולציה של טקסט ב-JavaScript. הם מאפשרים למפתחים לחפש, לאמת ולשנות מחרוזות בהתבסס על תבניות מוגדרות. מדריך זה מספק סקירה מקיפה של ביטויים רגולריים ב-JavaScript, הכוללת תחביר, שימוש וטכניקות מתקדמות.
מהם ביטויים רגולריים?
ביטוי רגולרי הוא רצף של תווים המגדיר תבנית חיפוש. תבניות אלו משמשות להתאמה ולמניפולציה של מחרוזות. ביטויים רגולריים נמצאים בשימוש נרחב בתכנות למשימות כגון:
- אימות נתונים: וידוא שקלט משתמש תואם לפורמטים ספציפיים (למשל, כתובות דוא"ל, מספרי טלפון).
- חילוץ נתונים: שליפת מידע ספציפי מטקסט (למשל, חילוץ תאריכים, כתובות URL או מחירים).
- חיפוש והחלפה: מציאה והחלפה של טקסט על בסיס תבניות מורכבות.
- עיבוד טקסט: פיצול, חיבור או שינוי של מחרוזות על בסיס כללים מוגדרים.
יצירת ביטויים רגולריים ב-JavaScript
ב-JavaScript, ניתן ליצור ביטויים רגולריים בשתי דרכים:
- שימוש בליטרל של ביטוי רגולרי: עוטפים את התבנית בלוכסנים קדמיים (
/). - שימוש בבנאי
RegExp: יוצרים אובייקטRegExpעם התבנית כמחרוזת.
דוגמה:
// שימוש בליטרל של ביטוי רגולרי
const regexLiteral = /hello/;
// שימוש בבנאי RegExp
const regexConstructor = new RegExp("hello");
הבחירה בין שתי השיטות תלויה בשאלה האם התבנית ידועה בזמן ההידור או נוצרת באופן דינמי. השתמשו בליטרל כאשר התבנית קבועה וידועה מראש. השתמשו בבנאי כאשר יש צורך לבנות את התבנית באופן תכנותי, במיוחד כאשר משלבים משתנים.
תחביר Regex בסיסי
ביטויים רגולריים מורכבים מתווים המייצגים את התבנית להתאמה. הנה כמה מרכיבי regex בסיסיים:
- תווים ליטרליים: מתאימים לתווים עצמם (לדוגמה,
/a/מתאים לתו 'a'). - תווי מטא (Metacharacters): בעלי משמעויות מיוחדות (לדוגמה,
.,^,$,*,+,?,[],{},(),\,|). - מחלקות תווים: מייצגות קבוצות של תווים (לדוגמה,
[abc]מתאים ל-'a', 'b' או 'c'). - כמתים (Quantifiers): מציינים כמה פעמים תו או קבוצה צריכים להופיע (לדוגמה,
*,+,?,{n},{n,},{n,m}). - עוגנים (Anchors): מתאימים למיקומים במחרוזת (לדוגמה,
^מתאים להתחלה,$מתאים לסוף).
תווי מטא נפוצים:
.(נקודה): מתאים לכל תו בודד פרט לתו ירידת שורה.^(גג): מתאים לתחילת המחרוזת.$(דולר): מתאים לסוף המחרוזת.*(כוכבית): מתאים לאפס או יותר מופעים של התו או הקבוצה הקודמים.+(פלוס): מתאים למופע אחד או יותר של התו או הקבוצה הקודמים.?(סימן שאלה): מתאים לאפס או מופע אחד של התו או הקבוצה הקודמים. משמש לתווים אופציונליים.[](סוגריים מרובעים): מגדירים מחלקת תווים, המתאימה לכל תו בודד בתוך הסוגריים.{}(סוגריים מסולסלים): מציינים את מספר המופעים להתאמה.{n}מתאים בדיוק n פעמים,{n,}מתאים ל-n או יותר פעמים,{n,m}מתאים בין n ל-m פעמים.()(סוגריים עגולים): מקבצים תווים יחד ולוכדים את תת-המחרוזת שהותאמה.\(לוכסן אחורי): מבצע escape לתווי מטא, ומאפשר להתאים אותם באופן ליטרלי.|(קו אנכי): פועל כאופרטור "או", ומתאים לביטוי שלפניו או לאחריו.
מחלקות תווים:
[abc]: מתאים לכל אחד מהתווים a, b, או c.[^abc]: מתאים לכל תו שאינו a, b, או c.[a-z]: מתאים לכל אות קטנה מ-a עד z.[A-Z]: מתאים לכל אות גדולה מ-A עד Z.[0-9]: מתאים לכל ספרה מ-0 עד 9.[a-zA-Z0-9]: מתאים לכל תו אלפאנומרי.\d: מתאים לכל ספרה (שווה ערך ל-[0-9]).\D: מתאים לכל תו שאינו ספרה (שווה ערך ל-[^0-9]).\w: מתאים לכל תו של מילה (אלפאנומרי וקו תחתון; שווה ערך ל-[a-zA-Z0-9_]).\W: מתאים לכל תו שאינו תו של מילה (שווה ערך ל-[^a-zA-Z0-9_]).\s: מתאים לכל תו רווח לבן (רווח, טאב, ירידת שורה וכו').\S: מתאים לכל תו שאינו רווח לבן.
כמתים:
*: מתאים לרכיב הקודם אפס או יותר פעמים. לדוגמה,a*מתאים ל-"", "a", "aa", "aaa" וכן הלאה.+: מתאים לרכיב הקודם פעם אחת או יותר. לדוגמה,a+מתאים ל-"a", "aa", "aaa", אך לא ל-"".?: מתאים לרכיב הקודם אפס או פעם אחת. לדוגמה,a?מתאים ל-"" או "a".{n}: מתאים לרכיב הקודם בדיוק *n* פעמים. לדוגמה,a{3}מתאים ל-"aaa".{n,}: מתאים לרכיב הקודם *n* או יותר פעמים. לדוגמה,a{2,}מתאים ל-"aa", "aaa", "aaaa" וכן הלאה.{n,m}: מתאים לרכיב הקודם בין *n* ל-*m* פעמים (כולל). לדוגמה,a{2,4}מתאים ל-"aa", "aaa" או "aaaa".
עוגנים:
^: מתאים לתחילת המחרוזת. לדוגמה,^Helloמתאים למחרוזות ש*מתחילות* ב-"Hello".$: מתאים לסוף המחרוזת. לדוגמה,World$מתאים למחרוזות ש*מסתימות* ב-"World".\b: מתאים לגבול מילה. זהו המיקום בין תו מילה (\w) לתו שאינו תו מילה (\W) או תחילת או סוף המחרוזת. לדוגמה,\bword\bמתאים למילה השלמה "word".
דגלים:
דגלי Regex משנים את התנהגותם של ביטויים רגולריים. הם מצורפים לסוף ליטרל ה-regex או מועברים כארגומנט שני לבנאי RegExp.
g(גלובלי): מתאים לכל המופעים של התבנית, לא רק לראשון.i(התעלם מאותיות רישיות/קטנות): מבצע התאמה שאינה תלוית רישיות (case-insensitive).m(רב-שורתי): מאפשר מצב רב-שורתי, שבו^ו-$מתאימים לתחילת וסוף כל שורה (המופרדת על ידי\n).s(dotAll): מאפשר לנקודה (.) להתאים גם לתווי ירידת שורה.u(יוניקוד): מאפשר תמיכה מלאה ביוניקוד.y(דביק): מתאים רק מהאינדקס המצוין במאפייןlastIndexשל ה-regex.
מתודות Regex ב-JavaScript
JavaScript מספקת מספר מתודות לעבודה עם ביטויים רגולריים:
test(): בודקת אם מחרוזת תואמת לתבנית. מחזירהtrueאוfalse.exec(): מבצעת חיפוש התאמה במחרוזת. מחזירה מערך המכיל את הטקסט שהותאם ואת הקבוצות שנלכדו, אוnullאם לא נמצאה התאמה.match(): מחזירה מערך המכיל את תוצאות ההתאמה של מחרוזת מול ביטוי רגולרי. מתנהגת באופן שונה עם ובלי הדגלg.search(): בודקת התאמה במחרוזת. מחזירה את האינדקס של ההתאמה הראשונה, או -1 אם לא נמצאה התאמה.replace(): מחליפה מופעים של תבנית במחרוזת חלופית או בפונקציה שמחזירה את המחרוזת החלופית.split(): מפצלת מחרוזת למערך של תת-מחרוזות על בסיס ביטוי רגולרי.
דוגמאות לשימוש במתודות Regex:
// test()
const regex = /hello/;
const str = "hello world";
console.log(regex.test(str)); // פלט: true
// exec()
const regex2 = /hello (\w+)/;
const str2 = "hello world";
const result = regex2.exec(str2);
console.log(result); // פלט: ["hello world", "world", index: 0, input: "hello world", groups: undefined]
// match() עם הדגל 'g'
const regex3 = /\d+/g; // מתאים לספרה אחת או יותר באופן גלובלי
const str3 = "There are 123 apples and 456 oranges.";
const matches = str3.match(regex3);
console.log(matches); // פלט: ["123", "456"]
// match() ללא הדגל 'g'
const regex4 = /\d+/;
const str4 = "There are 123 apples and 456 oranges.";
const match = str4.match(regex4);
console.log(match); // פלט: ["123", index: 11, input: "There are 123 apples and 456 oranges.", groups: undefined]
// search()
const regex5 = /world/;
const str5 = "hello world";
console.log(str5.search(regex5)); // פלט: 6
// replace()
const regex6 = /world/;
const str6 = "hello world";
const newStr = str6.replace(regex6, "JavaScript");
console.log(newStr); // פלט: hello JavaScript
// replace() עם פונקציה
const regex7 = /(\d+)-(\d+)-(\d+)/;
const str7 = "Today's date is 2023-10-27";
const newStr2 = str7.replace(regex7, (match, year, month, day) => {
return `${day}/${month}/${year}`;
});
console.log(newStr2); // פלט: Today's date is 27/10/2023
// split()
const regex8 = /, /;
const str8 = "apple, banana, cherry";
const arr = str8.split(regex8);
console.log(arr); // פלט: ["apple", "banana", "cherry"]
טכניקות Regex מתקדמות
קבוצות לכידה (Capturing Groups):
סוגריים עגולים () משמשים ליצירת קבוצות לכידה בביטויים רגולריים. קבוצות לכידה מאפשרות לחלץ חלקים ספציפיים מהטקסט שהותאם. המתודות exec() ו-match() מחזירות מערך שבו הרכיב הראשון הוא ההתאמה המלאה, והרכיבים הבאים הם קבוצות הלכידה.
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match[0]); // פלט: 2023-10-27 (ההתאמה המלאה)
console.log(match[1]); // פלט: 2023 (קבוצת הלכידה הראשונה - שנה)
console.log(match[2]); // פלט: 10 (קבוצת הלכידה השנייה - חודש)
console.log(match[3]); // פלט: 27 (קבוצת הלכידה השלישית - יום)
קבוצות לכידה עם שם (Named Capturing Groups):
תקן ES2018 הציג קבוצות לכידה עם שם, המאפשרות להקצות שמות לקבוצות לכידה באמצעות התחביר (?. זה הופך את הקוד לקריא וקל יותר לתחזוקה.
const regex = /(?\d{4})-(?\d{2})-(?\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match.groups.year); // פלט: 2023
console.log(match.groups.month); // פלט: 10
console.log(match.groups.day); // פלט: 27
קבוצות שאינן לוכדות (Non-Capturing Groups):
אם אתם צריכים לקבץ חלקים של regex מבלי ללכוד אותם (למשל, כדי להחיל כמת על קבוצה), אתם יכולים להשתמש בקבוצה שאינה לוכדת עם התחביר (?:...). זה מונע הקצאת זיכרון מיותרת לקבוצות שנלכדו.
const regex = /(?:https?:\/\/)?([\w\.]+)/; // מתאים ל-URL אך לוכד רק את שם הדומיין
const url = "https://www.example.com/path";
const match = regex.exec(url);
console.log(match[1]); // פלט: www.example.com
התניות מבט (Lookarounds):
התניות מבט הן קביעות ברוחב אפס המתאימות למיקום במחרוזת בהתבסס על תבנית שקודמת (lookbehind) או עוקבת (lookahead) למיקום זה, מבלי לכלול את תבנית ההתניה בהתאמה עצמה.
- התניית מבט קדמית חיובית (Positive Lookahead):
(?=...)מתאים אם התבנית בתוך ההתניה *עוקבת* אחר המיקום הנוכחי. - התניית מבט קדמית שלילית (Negative Lookahead):
(?!...)מתאים אם התבנית בתוך ההתניה *אינה עוקבת* אחר המיקום הנוכחי. - התניית מבט אחורית חיובית (Positive Lookbehind):
(?<=...)מתאים אם התבנית בתוך ההתניה *קודמת* למיקום הנוכחי. - התניית מבט אחורית שלילית (Negative Lookbehind):
(? מתאים אם התבנית בתוך ההתניה *אינה קודמת* למיקום הנוכחי.
דוגמה:
// התניית מבט קדמית חיובית: קבל את המחיר רק כאשר אחריו מופיע USD
const regex = /\d+(?= USD)/;
const text = "The price is 100 USD";
const match = text.match(regex);
console.log(match); // פלט: ["100"]
// התניית מבט קדמית שלילית: קבל את המילה רק כאשר לאחריה לא מופיע מספר
const regex2 = /\b\w+\b(?! \d)/;
const text2 = "apple 123 banana orange 456";
const matches = text2.match(regex2);
console.log(matches); // פלט: null מכיוון ש-match() מחזירה רק את ההתאמה הראשונה ללא הדגל 'g', וזה לא מה שאנחנו צריכים.
// כדי לתקן את זה:
const regex3 = /\b\w+\b(?! \d)/g;
const text3 = "apple 123 banana orange 456";
const matches3 = text3.match(regex3);
console.log(matches3); // פלט: [ 'banana' ]
// התניית מבט אחורית חיובית: קבל את הערך רק כאשר לפניו מופיע סימן $
const regex4 = /(?<=\$)\d+/;
const text4 = "The price is $200";
const match4 = text4.match(regex4);
console.log(match4); // פלט: ["200"]
// התניית מבט אחורית שלילית: קבל את המילה רק כאשר לפניה לא מופיעה המילה 'not'
const regex5 = /(?
התייחסויות לאחור (Backreferences):
התייחסויות לאחור מאפשרות להתייחס לקבוצות שנלכדו קודם לכן באותו ביטוי רגולרי. הן משתמשות בתחביר \1, \2, וכו', כאשר המספר מתאים למספר קבוצת הלכידה.
const regex = /([a-z]+) \1/;
const text = "hello hello world";
const match = regex.exec(text);
console.log(match); // פלט: ["hello hello", "hello", index: 0, input: "hello hello world", groups: undefined]
יישומים מעשיים של ביטויים רגולריים
אימות כתובות דוא"ל:
מקרה שימוש נפוץ לביטויים רגולריים הוא אימות כתובות דוא"ל. למרות ש-regex מושלם לאימות דוא"ל הוא מורכב ביותר, הנה דוגמה פשוטה:
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
console.log(emailRegex.test("test@example.com")); // פלט: true
console.log(emailRegex.test("invalid-email")); // פלט: false
console.log(emailRegex.test("test@sub.example.co.uk")); // פלט: true
חילוץ כתובות URL מטקסט:
ניתן להשתמש בביטויים רגולריים כדי לחלץ כתובות URL מתוך גוש טקסט:
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
const text = "Visit our website at https://www.example.com or check out http://blog.example.org.";
const urls = text.match(urlRegex);
console.log(urls); // פלט: ["https://www.example.com", "http://blog.example.org"]
ניתוח נתוני CSV:
ניתן להשתמש בביטויים רגולריים כדי לנתח נתוני CSV (ערכים מופרדים בפסיקים). הנה דוגמה לפיצול מחרוזת CSV למערך של ערכים, תוך טיפול בשדות עם מירכאות:
const csvString = 'John,Doe,"123, Main St",New York';
const csvRegex = /(?:"([^"]*(?:""[^"]*)*)")|([^,]+)/g; // regex מתוקן ל-CSV
let values = [];
let match;
while (match = csvRegex.exec(csvString)) {
values.push(match[1] ? match[1].replace(/""/g, '"') : match[2]);
}
console.log(values); // פלט: ["John", "Doe", "123, Main St", "New York"]
אימות מספרי טלפון בינלאומיים
אימות מספרי טלפון בינלאומיים הוא מורכב בגלל פורמטים ואורכים משתנים. פתרון חזק כולל לעתים קרובות שימוש בספרייה, אך regex פשוט יכול לספק אימות בסיסי:
const phoneRegex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
console.log(phoneRegex.test("+1 555 123 4567")); // פלט: true (דוגמה מארה"ב)
console.log(phoneRegex.test("+44 20 7946 0500")); // פלט: true (דוגמה מבריטניה)
console.log(phoneRegex.test("+81 3 3224 5000")); // פלט: true (דוגמה מיפן)
console.log(phoneRegex.test("123-456-7890")); // פלט: false
אימות חוזק סיסמה
ביטויים רגולריים שימושיים לאכיפת מדיניות חוזק סיסמאות. הדוגמה להלן בודקת אורך מינימלי, אותיות רישיות, אותיות קטנות ומספר.
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;
console.log(passwordRegex.test("P@ssword123")); // פלט: true
console.log(passwordRegex.test("password")); // פלט: false (אין אות גדולה או מספר)
console.log(passwordRegex.test("Password")); // פלט: false (אין מספר)
console.log(passwordRegex.test("Pass123")); // פלט: false (אין אות קטנה)
console.log(passwordRegex.test("P@ss1")); // פלט: false (פחות מ-8 תווים)
טכניקות לאופטימיזציה של Regex
ביטויים רגולריים יכולים להיות יקרים מבחינה חישובית, במיוחד עבור תבניות מורכבות או קלטים גדולים. הנה כמה טכניקות לאופטימיזציה של ביצועי regex:
- היו ספציפיים: הימנעו משימוש בתבניות כלליות מדי שעלולות להתאים ליותר מהרצוי.
- השתמשו בעוגנים: עגנו את ה-regex לתחילת או סוף המחרוזת בכל הזדמנות אפשרית (
^,$). - הימנעו מנסיגה (Backtracking): צמצמו את הנסיגה על ידי שימוש בכמתים רכושניים (למשל,
++במקום+) או קבוצות אטומיות ((?>...)) במידת הצורך. - הדרו פעם אחת: אם אתם משתמשים באותו regex מספר פעמים, הדרו אותו פעם אחת ועשו שימוש חוזר באובייקט
RegExp. - השתמשו במחלקות תווים בחוכמה: מחלקות תווים (
[]) הן בדרך כלל מהירות יותר מאשר חלופות (|). - שמרו על פשטות: הימנעו מביטויים רגולריים מורכבים מדי שקשה להבין ולתחזק. לפעמים, פירוק משימה מורכבת למספר ביטויים רגולריים פשוטים יותר או שימוש בטכניקות אחרות של מניפולציית מחרוזות יכול להיות יעיל יותר.
טעויות נפוצות ב-Regex
- שכחה לבצע escape לתווי מטא: אי ביצוע escape לתווים מיוחדים כמו
.,*,+,?,$,^,(,),[,],{,},|, ו-\כאשר רוצים להתאים אותם באופן ליטרלי. - שימוש יתר ב-
.(נקודה): הנקודה מתאימה לכל תו (פרט לירידת שורה במצבים מסוימים), מה שעלול להוביל להתאמות לא צפויות אם לא משתמשים בה בזהירות. היו ספציפיים יותר במידת האפשר באמצעות מחלקות תווים או תבניות מגבילות יותר. - חמדנות (Greediness): כברירת מחדל, כמתים כמו
*ו-+הם חמדנים ויתאימו כמה שיותר. השתמשו בכמתים עצלנים (*?,+?) כאשר אתם צריכים להתאים למחרוזת הקצרה ביותר האפשרית. - שימוש שגוי בעוגנים: אי הבנה של התנהגות
^(תחילת מחרוזת/שורה) ו-$(סוף מחרוזת/שורה) יכולה להוביל להתאמה שגויה. זכרו להשתמש בדגלm(רב-שורתי) בעת עבודה עם מחרוזות מרובות שורות ורצון ש-^ו-$יתאימו לתחילת וסוף כל שורה. - אי טיפול במקרי קצה: אי התחשבות בכל תרחישי הקלט האפשריים ומקרי הקצה יכולה להוביל לבאגים. בדקו את הביטויים הרגולריים שלכם ביסודיות עם מגוון קלטים, כולל מחרוזות ריקות, תווים לא חוקיים ותנאי גבול.
- בעיות ביצועים: בניית ביטויים רגולריים מורכבים ולא יעילים מדי עלולה לגרום לבעיות ביצועים, במיוחד עם קלטים גדולים. בצעו אופטימיזציה לביטויים הרגולריים שלכם על ידי שימוש בתבניות ספציפיות יותר, הימנעות מנסיגה מיותרת, והידור ביטויים רגולריים שנמצאים בשימוש חוזר.
- התעלמות מקידוד תווים: אי טיפול נכון בקידודי תווים (במיוחד יוניקוד) יכול להוביל לתוצאות בלתי צפויות. השתמשו בדגל
uבעת עבודה עם תווי יוניקוד כדי להבטיח התאמה נכונה.
סיכום
ביטויים רגולריים הם כלי רב ערך להתאמת תבניות ולמניפולציה של טקסט ב-JavaScript. שליטה בתחביר ובטכניקות של regex מאפשרת לפתור ביעילות מגוון רחב של בעיות, החל מאימות נתונים ועד לעיבוד טקסט מורכב. על ידי הבנת המושגים שנדונו במדריך זה ותרגול עם דוגמאות מהעולם האמיתי, תוכלו להפוך למיומנים בשימוש בביטויים רגולריים כדי לשפר את כישורי הפיתוח שלכם ב-JavaScript.
זכרו שביטויים רגולריים יכולים להיות מורכבים, ולעתים קרובות כדאי לבדוק אותם ביסודיות באמצעות בודקי regex מקוונים כמו regex101.com או regexr.com. זה מאפשר לכם לדמיין את ההתאמות ולנפות באגים בכל בעיה ביעילות. קידוד מהנה!