مقدمه

در جاوااسکریپت، نوع string برای کار با متن و مقادیر متنی تدارک دیده شده است. یادآوری می‌کنم که string یک نوع primitive و immutable یا تغییرناپذیر است. در مورد ماهیت تغییرناپذیر string در این درس صحبت خواهیم کرد. مقادیر string رشته‌های متنی هستند که به‌اختصار رشته گفته می‌شوند. یک رشته دنباله‌ای است از کاراکترهای یونیکد که می‌توان از طریق اندیس به آنها دسترسی داشت. در درس نوع‌های داده در جاوااسکریپت یک معرفی مختصر از نوع string بیان شد و در این درس قصد داریم جزئیات بیشتری در این مورد ارائه دهیم.

روش‌های ایجاد مقادیر رشته‌ای

یادآوری می‌کنم که در جاوااسکریپت می‌توان مقادیر متنی را درون کوتیشن‌های تکی، جفتی و بک‌تیک قرار داد.

Copy Icon strings.js
let single = 'single-quoted';
let double = "double-quoted";
            
let backticks = `backticks`;

کوتیشن‌های تکی و جفتی کاملاً مشابه هم هستند اما بک‌تیک‌ها این امکان را فراهم می‌کنند که عبارات (expressions) را با استفاده از سینتکس ${expression} درون رشته‌ها قرار دهیم. با این کار، عبارت مورد نظر ارزیابی شده و مقدارش به بخشی از رشته تبدیل می‌شود.

Copy Icon strings.js
let x = 5; 
let y = 6; 
            
console.log(`5 * 6 = ${5 * 6}`);  // 5 * 6 = 30

یک مزیت دیگر بک‌تیک‌ها این است که امکان نوشتن متن در بیش از یک خط را فراهم می‌کنند.

Copy Icon strings.js
let languageList = `Languages:
  * JavaScript
  * Rust
  * Python
  `;
console.log(languageList);

اگر این مثال را اجرا کنیم، خواهیم دید که رشته‌ی متنی به همان شکلی که نوشته شده، با حفظ شکست خط‌ها و فاصله‌ها، در خروجی نمایش داده می‌شود.

Langages:
  * JavaScript
  * Rust
  * Python
          

اما اگر این کار را با استفاده از کوتیشن‌های تکی و جفتی انجام دهیم، با خطا مواجه می‌شویم. البته می‌توان با استفاده از کاراکتر \n در رشته‌های ایجاد شده با کوتیشن‌های تکی و جفتی شکست خط ایجاد کرد.

Copy Icon strings.js
let languageList = "Languages:\n * JavaScript\n * Rust\n * Python";

console.log(languageList);

\n یک کاراکتر گریز (escape character) است. کاراکترهای گریز در جاوااسکریپت برای نمایش کاراکترهای خاص در رشته‌ها استفاده می‌شوند. این کاراکترها با یک بک‌اسلش (\) شروع می‌شوند تا به جاوااسکریپت بفهمانند که کاراکتر بعد از آن معنای خاصی دارد و به عنوان بخشی از رشته به صورت مستقیم نمایش داده نمی‌شود. مهمترین کاراکترهای گریز عبارتند از:

  • \n برای ایجاد یک خط جدید (new line) در رشته‌ها کاربرد دارد.
  • \t با ایجاد یک Tab افقی بین کاراکترهای رشته فاصله ایجاد می‌کند.
  • \' و \" و \` برای ایجاد کوتیشن‌های تکی، جفتی و بک‌تیک در رشته‌ها کاربرد دارند.
  • \\ یک بک‌اسلش در رشته ایجاد می‌کند.
  • \b کاراکتر قبلی را از رشته حذف می‌کند.
  • \uxxxx برای درج کاراکترها از طریق کدهای یونیکد کاربرد دارد. برای مثال، \u00A9 کاراکتر کپی‌رایت را در رشته نمایش می‌دهد.

در مثال زیر از این کاراکترهای گریز استفاده شده است.

Copy Icon strings.js
let str = "New line character: Hello\nWorld";
console.log(str);
            
str = "Horizontal tab character: Hello\tWorld";
console.log(str);
            
str = 'Apostrophe character: I\'m danial';
console.log(str);
            
str = 'Backslash character: D:\\Books\\title.pdf';
console.log(str);
            
str = 'Unicode character: \u00A9 2024';
console.log(str);

نتیجه‌ی اجرای این کد به صورت زیر خواهد بود.

New line character: Hello
World
Horizontal tab character: Hello   World
Apostrophe character: I'm danial
Backslash character: D:\Books\title.pdf
Unicode character: © 2024
          

دسترسی به کاراکترهای رشته

یک رشته دنباله‌ای از کاراکترهاست و دسترسی به هر کاراکتر رشته از طریق اندیس آن کاراکتر ممکن است. اندیس اولین کاراکتر یک رشته برابر با صفر و اندیس دومین کاراکتر برابر با 1 و به طور کلی، اندیس کاراکتر n-ام برابر با n-1 است.

Copy Icon strings.js
let str = "Hello";
console.log(str[0]);  // H
console.log(str[1]);  // e

یک روش دیگر هم برای دسترسی به کاراکترهای رشته‌ها وجود دارد و آن استفاده از متدی به نام at() است. متد at() نسبت به براکت‌ها یک مزیت ویژه دارد و آن امکان استفاده از اندیس‌های منفی است که باعث می شود شمارش از انتهای رشته شروع شود.

Copy Icon strings.js
let str = "Hello";
console.log(str.at(-1));   // o
console.log(str.at(-5));   // H

اندیس آخرین عنصر یک رشته همواره برابر با -1 است و این یکی از مزایای اندیس‌گذاری منفی است که همیشه می‌توان بدون دانستن طول رشته، به آخرین کاراکتر رشته دسترسی داشت. توجه داشته باشید که اندیس‌گذاری منفی با براکت‌ها کار نمی‌کند و همیشه مقدار undefined را برمی‌گرداند.

حالا که از طول رشته‌ها نام بردیم، اجازه دهید شما را با پراپرتی length آشنا کنم که طول رشته، یعنی تعداد کاراکترهای رشته را برمی‌گرداند.

Copy Icon strings.js
let str = "Hello";
console.log(str.length); // 5

کسانی که سابقه‌ی کار با زبان‌های دیگری مثل پایتون را داشته باشند، گاهی اوقات پراپرتی length را به صورت یک متد length() به کار می‌برند و با خطا مواجه می‌شوند. دقت داشته باشید که در جاوااسکریپت، length یک پراپرتی عددی است نه یک متد و بنابراین، به پرانتز نیاز ندارد.

تغییرناپذیری رشته‌ها

در جاوااسکریپت، رشته‌ها تغییرناپذیر یا immutable هستند؛ یعنی امکان ویرایش درجای آنها وجود ندارد. بنابراین، کد زیر کار نمی‌کند.

Copy Icon strings.js
let str = "Hello";
str[0] = 'h';
console.log(str); // Hello

اگر این کد را اجرا کنید، خواهید دید که گزاره‌ی دوم هیچ تأثیری ندارد و رشته همچنان به صورت Hello چاپ می‌شود. حتی اگر از متدهایی که نوع string برای ویرایش رشته‌ها ارائه داده، استفاده کنیم، باز هم نتیجه‌ای نخواهد داشت.

Copy Icon strings.js
let str = "Hello";
str.replace('H', 'h');
console.log(str); // Hello

در اینجا از متدی به نام replace() استفاده کرده‌ایم که همانطور که نامش هم نشان می‌دهد، برای جابجایی یک کاراکتر رشته با یک کاراکتر دیگر کاربرد دارد، اما باز هم تغییر مورد نظر روی رشته انجام نشده است. در واقع، همانطور که گفته شد، علت کار نکردن دو مثال قبل، ماهیت تغیرناپذیر رشته‌هاست که ویرایش درجای آنها را غیرممکن می‌کند. برای اینکه مثال بالا مطابق انتظار کار کند، باید آن را به صورت زیر ویرایش کنیم.

Copy Icon strings.js
let str = "Hello";
str = str.replace('H', 'h');
console.log(str); // hello

متدهای مربوط به رشته‌ها

در این بخش قصد داریم مهمترین متدهای نوع string را معرفی کنیم و کاربرد آنها را ببینیم. اغلب این متدها از نوع نمونه‌ای (غیر استاتیک) هستند و بنابراین، به فرم str.method() که در آن str یک رشته است، قابل استفاده هستند.

تغییر بزرگی و کوچکی حروف

کار بررسی متدهای string را با دو متد toUpperCase() و toLowerCase() شروع می‌کنیم که کاربردشان از نامشان مشخص است. متد toUpperCase() رشته را به یک رشته با حروف بزرگ تبدیل می‌کند و متد toLowerCase() رشته را به رشته‌ای با حروف کوچک تبدیل می‌کند. در مثال زیر از این دو متد استفاده شده است.

Copy Icon strings.js
console.log('hello'.toUpperCase()); // HELLO
console.log('Hello'.toLowerCase()); // hello
            
let str = "Interface".toUpperCase();
console.log(str); // INTERFACE

جستجو برای یک زیررشته

متدهایی که در این بخش معرفی می‌کنیم، برای جستجوی زیررشته‌ها کاربرد دارند؛ یعنی وقتی می‌خواهیم ببینیم آیا عبارت متنی مورد نظرمان در یک رشته وجود دارد یا نه، از این متدها استفاده می‌کنیم. اولین متد indexOf() نام دارد و دارای فرم کلی زیر است.

str.indexOf(substr, pos)

این متد به دنبال عبارت substr در رشته‌ی str می‌گردد و جستجو را از اندیس pos شروع می‌کند. اگر عبارت substr در رشته باشد، اندیسی را که substr از آن شروع شده برمی‌گرداند و در غیر این صورت مقدار -1 را. در ضمن، pos یک پارامتر اختیاری است که اگر آن را وارد نکنیم، برابر با صفر در نظر گرفته شده و بنابراین، جستجو از ابتدای رشته شروع می‌شود.

Copy Icon strings.js
let str = 'Widget with id';

console.log(str.indexOf('Widget')); // 0
console.log(str.indexOf('widget')); // -1
            
console.log(str.indexOf("id"));     // 1
console.log(str.indexOf("id", 2));  // 12

حاصل اولین متد log() در مثال بالا مقدار صفر است؛ چون عبارت Widget در ابتدای رشته‌ی str قرار دارد. اما حاصل دومین متد log() مقدار -1 است و این نشان می‌دهد که برای متد indexOf() بزرگی و کوچکی حروف مهم است و بنابراین، از نظر این متد widget با Widget متفاوت است. سومین متد log() هم خروجی 1 را تولید می‌کند اما آخرین متد log() مقدار 12 را نمایش می‌دهد؛ چون این بار مقدار 2 برای پارامتر pos تعیین شده و نتیجتاً جستجو از سومین کاراکتر رشته شروع می‌شود.

اگر همه‌ی تکرارهای یک زیررشته در یک رشته را بخواهیم، باید از حلقه‌هایی مانند while و for استفاده کنیم که در درس بعدی معرفی می‌شوند.

یک روش مدرن‌تر برای جستجوی زیررشته‌ها، متد includes() است که دارای فرم کلی زیر است.

str.includes(substr, pos)

اگر عبارت substr در رشته‌ی str وجود داشته باشد، این متد مقدار true و در غیر این صورت مقدار false را برمی‌گرداند. پارارمتر pos نیز مشخص‌کننده‌ی مکان شروع جستجو در رشته است.

Copy Icon strings.js
let str = 'Widget with id';

console.log(str.includes('Widget')); // true
console.log(str.includes('widget')); // false
            
console.log(str.includes("id"));     // true
console.log(str.includes("id", 2));  // true

نتیجه‌ی اجرای کد بالا به صورت زیر خواهد بود که علت تولید این خروجی باید برایتان واضح باشد.

true
false
true
true
          

دو متد دیگر هم داریم که مشابه یا به عبارت بهتر، مکمل متد includes() هستند. یکی از این متدها startsWith() نام دارد و دیگری endsWith() و فرم کلی این متدها به صورت زیر است.

str.startsWith(substr)
str.endsWith(substr)

متد اول بررسی می‌کند که آیا رشته‌ی str با مقدار substr شروع می‌شود یا خیر. در صورت مثبت بودن جواب، مقدار true و در غیر این صورت مقدار false برگردانده می‌شود. متد دوم هم به همین ترتیب، بررسی می‌کند که آیا رشته‌ی str با مقدار substr به پایان رسیده یا خیر.

Copy Icon strings.js
let str = 'Widget with id';

console.log(str.startsWith('Widget')); // true
console.log(str.endsWith('ab'));       // false

استخراج زیررشته‌ها

در این بخش سه متد معرفی می‌کنیم که برای استخراج زیرشته از رشته‌ها کاربرد دارند؛ یعنی برای وقتی که می‌خواهیم بخشی از یک رشته را از آن استخرج کنیم. این متدها عبارتند از: slice()، substring() و substr().

کار را با متد slice() شروع می‌کنیم که دارای فرم کلی زیر است.

str.slice(start, end)

این متد از اندیس start تا end-1 را از رشته استخراج می‌کند؛ یعنی اندیس پایانی که در اینجا end نامگذاری شده، در زیررشته‌ی استخراج‌شده وجود نخواهد داشت.

Copy Icon strings.js
let str = "stringify";
console.log(str.slice(0, 5)); // 'strin'
console.log(str.slice(0, 1)); // 's'

اگر فقط یک آرگومان برای متد slice() فراهم کنیم، یعنی از این متد به صورت slice(start) استفاده کنیم، از start تا انتهای رشته استخراج می‌شود. در ضمن، پارامترهای start و end می‌توانند مقادیر منفی هم باشند که در این صورت، شمارش اندیس از انتهای رشته انجام می‌شود.

Copy Icon strings.js
let str = "stringify";
console.log(str.slice(2));      // 'ringify'
console.log(str.slice(-4, -1)); // 'gif'

متد بعدی substring() نام دارد و دارای فرم کلی زیر است.

str.substring(start, end)

ظاهراً که متد substring() مشابه متد slice() است و واقعاً هم همینطور است. یعنی پارامترهای start و end به‌ترتیب، مشخص‌کننده‌ی اندیس ابتدایی و انتهایی زیررشته‌ی استخراجی هستند و خود end هم محاسبه نمی‌شود. اما دو تفاوت نسبتاً کوچک بین این دو متد وجود دارد. اول این که در مورد متد substring() مقدار start می‌تواند از end بزرگ‌تر باشد که در این صورت، به طور خودکار جابجا می‌شوند. برای مثال، substring(6, 2) معادل با substring(2, 6) در نظر گرفته می‌شود. تفاوت دیگر دو متد این است که بر خلاف slice() متد substring() از مقادیر منفی برای پارامترهای start و end پشتیبانی نمی‌کند و مقادیر منفی را برابر با صفر فرض می‌کند.

و بالاخره آخرین متدی که به استخراج زیررشته‌ها مربوط است، متد substr() است که دارای فرم کلی زیر است.

str.substr(start, length)

برای این متد، پارامتر start اندیس شروع زیررشته‌ی استخراجی است و پارامتر length طول زیررشته‌ی استخراجی را تعیین می‌کند. یعنی با شروع از start یک زیررشته به طول length از رشته استخراج می‌شود.

Copy Icon strings.js
let str = "stringify";
console.log(str.substr(2, 4));  // 'ring'
console.log(str.substr(-4, 3)); // 'gif'

پس ما سه متد برای استخراج زیررشته از رشته‌ها داریم که تقریباً مشابه هم هستند و حالا این سوال پیش می‌آید که بهتر است از کدام‌یک از این متدها استفاده کنیم؟

متد substr() یک ضعف خاص دارد و آن این است که این متد در مستندات ES وجود ندارد، بلکه بحشی از Annex است که ویزگی‌هایی را تعریف می‌کند که مختص مرورگرها هستند و به دلایل تاریخی نگه داشته شده‌اند. بنابراین، لااقل از نظر تئوری این امکان وجود دارد که محیط‌های مستقل از مرورگرها از آن پشتیبانی نکنند. در vscode از عبارت deprecated یا منقضی‌شده برای این متد استفاده می‌شود تا شما را تشویق کند از این متد استفاده نکنید. پس، بهتر است آن را کنار بگذارید. از بین دو متد باقیمانده هم متد slice() با توجه به مزیت پشتیبانی از پارامترهای منفی به متد substring() ارجحیت پیدا می‌کند. بنابراین، می‌توان گفت در عمل، تسلط به متد slice() برای استخرج زیررشته‌ها کفایت می‌کند. اما به هر حال، چون امکان رؤیت دو متد دیگر در کدهای دیگران و کتابخانه‌های جاوااسکریپتی وجود دارد، باید با آنها هم آشنا باشیم.