אובייקט `String`

אובייקט `String`

מאמר זה מסביר את אובייקט ה-String.

ההסבר מכסה הכל מהבסיס ועד טכניקות מתקדמות, כולל מלכודות שקשורות ל-Unicode וביטויים רגולריים, צעד אחר צעד ובצורה מובנת.

YouTube Video

אובייקט String

מחרוזות ב-JavaScript הן אחד הסוגים הנפוצים ביותר בשימוש בפיתוח יומיומי.

הבדל בין מחרוזות פרימיטיביות לאובייקטים של String

מחרוזות פרימיטיביות (כמו "hello") מתנהגות אחרת מאובייקטי מעטפת כמו new String("hello"). בדרך כלל עדיף להשתמש בפרימיטיבים ואין כמעט צורך להשתמש בצורה האובייקטית.

1// Primitive string
2const a = "hello";
3
4// String wrapper object
5const b = new String("hello");
6
7console.log(typeof a); // "string"
8console.log(typeof b); // "object"
9console.log(a === b);  // false — wrapper objects are not strictly equal
  • הקוד הזה מראה את ההבדל בסוג בין פרימיטיב למעטפת, ואיך הם מתנהגים בהשוואה מחמירה. ברוב המקרים כדאי להימנע משימוש ב-new String() ולהיצמד לפרימיטיבים.

דרכים ליצור מחרוזות (ערכי טקסט ומחרוזות תבנית)

מחרוזות תבנית שימושיות להטמעת משתנים וכתיבת טקסט רב-שורות. ניתן לשלב משתנים ולחשב ביטויים בצורה אינטואיטיבית.

1const name = "Alice";
2const age = 30;
3
4// Template literal
5const greeting = `Name: ${name}, Age: ${age + 1}`;
6
7console.log(greeting); // "Name: Alice, Age: 31"
  • מחרוזות תבנית ברורות מאוד ונוחות לבניית מחרוזות מורכבות, כולל טקסט רב-שורות.

שיטות נפוצות (חיפוש וחילוץ תת-מחרוזות)

לאובייקט ה-String יש הרבה פונקציות בסיסיות.

 1const text = "Hello, world! Hello again.";
 2
 3// search
 4console.log(text.indexOf("Hello"));       // 0
 5console.log(text.indexOf("Hello", 1));    // 13
 6console.log(text.includes("world"));      // true
 7console.log(text.startsWith("Hello"));    // true
 8console.log(text.endsWith("again."));     // true
 9
10// slice / substring
11console.log(text.slice(7, 12));           // "world"
12console.log(text.substring(7, 12));       // "world"
  • slice ו-substring דומות, אבל מטפלות באינדקסים שליליים בצורה שונה. slice מחשב ערכים שליליים כמיקומים מהסוף. חשוב להבהיר לעצמך איזו מהן להשתמש.

פיצול וחיבור (split / join)

נהוג לפצל מחרוזת למערך לצורך עיבוד ואז לחבר מחדש.

1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
  • תבנית נפוצה היא להשתמש ב-split לפיצול מחרוזת, לעבד את המערך עם map או filter, ואז להשתמש ב-join לחיבור מחדש.

החלפה וביטויים רגולריים

הפונקציה replace מחליפה רק את ההתאמה הראשונה. אם ברצונך להחליף את כל ההתאמות, יש להשתמש בדגל g בביטוי רגולרי. על ידי העברת פונקציה כתחליף, ניתן לבצע גם החלפות דינמיות.

 1const s = "foo 1 foo 2";
 2
 3// replace first only
 4console.log(s.replace("foo", "bar")); // "bar 1 foo 2"
 5
 6// replace all using regex
 7console.log(s.replace(/foo/g, "bar")); // "bar 1 bar 2"
 8
 9// replace with function
10const r = s.replace(/\d+/g, (match) => String(Number(match) * 10));
11console.log(r);    // "foo 10 foo 20"
  • עם החלפה דינמית באמצעות פונקציה, ניתן לכתוב קוד תמציתי שמנתח ומשנה התאמות.

שינוי אותיות ונרמול

לתמיכה וריבוי שפות והשוואה, בנוסף ל־toLowerCase ו־toUpperCase, גם נרמול יוניקוד (normalize) הוא חשוב. זה נדרש במיוחד בעת השוואת תווים עם סימני טעם (accent).

 1// Case conversion example:
 2// "\u00DF" represents the German letter "ß".
 3// In some locales, converting "ß" to uppercase becomes "SS".
 4// JavaScript follows this behavior.
 5console.log("\u00DF");
 6console.log("\u00DF".toUpperCase()); // "SS"
 7
 8// Unicode normalization example:
 9// "e\u0301" is "e" + a combining acute accent.
10// "\u00e9" is the precomposed character "é".
11// These two look the same but are different code point sequences.
12const a = "e\u0301";
13const b = "\u00e9";
14
15console.log(a === b);   // false: different underlying code points
16console.log(a.normalize() === b.normalize()); // true: normalized to the same form
  • ייצוגים שונים ב-Unicode, כמו ליגטורות ותווים משולבים, לא יהיו שווים כפי שהם, לכן השתמש ב-normalize() לפני השוואות.

Unicode ונקודות קוד (טיפול בזוגות סורגייט)

מחרוזות ב-JavaScript הן רצפים של יחידות קוד UTF-16, ולכן תווים מסוימים כמו אמוג'י תופסים שתי יחידות קוד עבור תו בודד. לטיפול ביחידות תו אמיתיות יש להשתמש ב-Array.from, באופרטור Spread או בלולאת for...of.

 1// Emoji composed with multiple code points:
 2// "\u{1F469}" = woman, "\u{200D}" = Zero Width Joiner (ZWJ),
 3// "\u{1F4BB}" = laptop. Combined, they form a single emoji: 👩‍💻
 4const s = "\u{1F469}\u{200D}\u{1F4BB}";
 5console.log(s);
 6
 7// Length in UTF-16 code units (not actual Unicode characters):
 8// Because this emoji uses surrogate pairs + ZWJ, the length may be > 1.
 9console.log("Length:", s.length);
10
11// Iterate by Unicode code points (ES6 for...of iterates code points):
12// Each iteration gives a full Unicode character, not UTF-16 units.
13for (const ch of s) {
14  console.log(ch);
15}
16
17// Convert to an array of Unicode characters:
18console.log(Array.from(s));
  • הפונקציה length מחזירה את מספר יחידות הקוד, לכן במספרים עבור אמוג'י או ליגטורות יתכן שלא תקבל את התוצאה המצופה. for...of ו-Array.from מטפלות בדבר הקרוב לדמויות מוצגות (אשכולות גרפים), אך אם דרושה תמיכה מלאה יש להשתמש בספריה מתמחה.

החלפה בטוחה עם ביטוי רגולרי (בעת טיפול בקלט משתמש)

אם תשכח להימנע מתווים מיוחדים כאשר אתה משלב קלט מהמשתמש בתוך ביטוי רגולרי, זה עלול להוביל להתנהגות בלתי צפויה ולפגיעויות. תמיד בצע Escape לקלט המשתמש לפני השימוש בו בתבנית.

1function escapeRegex(s) {
2  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3}
4
5const userInput = "a+b";
6const pattern = new RegExp(escapeRegex(userInput), "g");
7console.log("a+b a+b".replace(pattern, "X")); // "X X"
  • אל תשתמש במחרוזות משתמש ישירות בביטויים רגולריים; תמיד בצע להן Escape לפני בניית ה-regex.

טיפים לביצועים: שרשור ומחרוזות תבנית

בעת שרשור של הרבה מחרוזות קטנות ברצף, כדאי להניח אותן במערך ולהשתמש ב-join למען יעילות. מצד שני, מחרוזות תבנית קריאות מאוד ומהירות די הצורך ברוב המקרים.

 1// concatenation in loop (less ideal)
 2let s = "";
 3for (let i = 0; i < 1000; i++) {
 4  s += i + ",";
 5}
 6
 7// using array + join (often faster for many pieces)
 8const parts = [];
 9for (let i = 0; i < 1000; i++) {
10  parts.push(i + ",");
11}
12const s2 = parts.join("");
  • מנועי JavaScript מודרניים מאוד אופטימליים, כך שאין צורך לדאוג מהביצועים בשל מספר קטן של שרשורים. עם זאת, אם עליך לשרשר עשרות אלפי פעמים, השימוש ב-join יעיל יותר.

טכניקות שימושיות: ריווח, חיתוך (trim) וחזרה (repeat)

trim, padStart, padEnd ו־repeat הן שיטות נוחות ושימושיות במיוחד בעיבוד מחרוזות יום־יומי. לעיתים קרובות משתמשים בהן במצבים פרקטיים, כמו עיצוב ערכי קלט או תקנון פורמטים של פלט.

1console.log("  hello  ".trim());       // "hello"
2console.log("5".padStart(3, "0"));     // "005"
3console.log("x".repeat(5));            // "xxxxx"
  • אפשר להשתמש בשיטות אלו לנרמול קלט מטפסים או ליצירת פלט ברוחב קבוע.

השוואת מחרוזות (השוואה לפי אזור/שפה)

localeCompare מועיל להשוואת מחרוזות לפי סדר מילוני בשפות שונות. ניתן להגדיר שפה ואפשרויות רגישות (כמו רגישות לאותיות גדולות/קטנות).

1console.log(
2  "\u00E4".localeCompare("z", "de")
3); // may be -1 or other depending on locale
4
5console.log(
6  "a".localeCompare("A", undefined, { sensitivity: "base" })
7); // 0
  • להשוואות בינלאומיות, השתמש ב-localeCompare וציין את האזור (Locale) וההגדרות הרלוונטיות.

דוגמה מעשית: המרת שורת CSV לאובייקט (תהליך עבודה מעשי)

מקרה שימוש נפוץ הוא ניתוח שורת CSV יחידה לאובייקט באמצעות שילוב של split, trim ו-map. בשדות עם גרשיים או קבצי CSV מורכבים, השתמש בפורסר CSV ייעודי.

 1// simple CSV parse (no quotes handling)
 2function parseCsvLine(line, headers) {
 3  const values = line.split(",").map(v => v.trim());
 4  const obj = {};
 5  headers.forEach((h, i) => obj[h] = values[i] ?? null);
 6  return obj;
 7}
 8
 9const headers = ["name", "age", "city"];
10const line = " Alice , 30 , New York ";
11console.log(parseCsvLine(line, headers));
12// { name: "Alice", age: "30", city: "New York" }
  • שיטה זו מתאימה ל-CSV פשוט בלבד, אך זכור שאינה תומכת בפסיקים בתוך שדות עם גרשיים.

מלכודות נפוצות

ישנן התנהגויות ומאפיינים שקל להחמיץ בטיפול במחרוזות ב-JavaScript. כדי להימנע מבאגים לא צפויים, חשוב לזכור את הנקודות הבאות.

  • שימוש ב-new String() עלול לגרום לתוצאות לא נכונות בבדיקות סוג או השוואות. ברוב המקרים סוג מחרוזת פרימיטיבית מספיק בהחלט.
  • ב-Unicode, תו נראה אחד עשוי להכיל מספר יחידות קוד. לכן הערך שתחזיר length עשוי לא להתאים למספר התווים המוצגים בפועל.
  • בעת הכללת קלט משתמש בתוך ביטוי רגולרי, בצע עליו Escape קודם כל.
  • String.prototype.replace() מחליף רק את ההתאמה הראשונה כברירת מחדל. אם ברצונך להחליף את כל המופעים, השתמש בדגל ‎/g‎ בביטוי הרגולרי שלך.
  • מחרוזות אינן ניתנות לשינוי, לכן כל פעולה מחזירה מחרוזת חדשה. חשוב תמיד להקצות את הערך המוחזר.

סיכום

אף שמחרוזות ב-JavaScript נראות פשוטות, חשוב להבין את המאפיינים שלהן ביחס ל-Unicode ולאי-שינוי. באמצעות שליטה בבסיס, ניתן לשפר משמעותית את האמינות והקריאות של עיבוד המחרוזות שלך.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video