۲۶ اوت ۲۰۲۲

رشته‌ها

در جاوااسکریپت، داده‌ی متنی به عنوان رشته (string) ذخیره می‌شود. نوع جداگانه‌ای برای یک کاراکتر مفرد وجود ندارد.

فرمت درونی برای رشته‌ها همیشه UTF-16 است، و به رمزگذاری صفحه بستگی ندارد.

کوتیشن‌ها

بیایید انواع کوتیشن‌ها را یادآوری کنیم.

رشته‌ها می‌توانند در کوتیشن‌های تکی، دوتایی یا backtickها محصور شوند:

let single = 'کوتیشن تکی';
let double = "کوتیشن دوتایی";

let backticks = `هاbacktick`;

کوتیشن‌های تکی و دوتایی اساسا یکسان هستند. اگرچه، backtickها، با پیچیدن هر عبارتی در {...}$، به ما اجازه می‌دهند که آن عبارت را درون رشته قرار دهیم:

function sum(a, b) {
  return a + b;
}

alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.

یکی دیگر از مزایای استفاده از backtickها این است که اجازه می‌دهند تا رشته را در چند خط بنویسیم:

let guestList = `مهمان‌ها:
 * John
 * Pete
 * Mary
`;

alert(guestList); // لیستی از مهمان‌ها، در چند خط

طبیعی به نظر می‌رسد نه؟ اما کوتیشن‌های تکی یا دوتایی این چنین کار نمی‌کنند.

اگر ما با استفاده از آنها تلاش کنیم در چند خط بنویسیم، یک ارور به وجود خواهد آمد:

let guestList = "Guests: // Error: Unexpected token ILLEGAL
  * John";

کوتیشن‌های تکی و دوتایی از زمان بسیار قدیم در زبان وجود داشتند زمانی که نیاز به رشته‌های چند خطی خیلی به چشم نمی‌آمد. Backtickها بعدها به وجود آمدند و به این ترتیب چند کاره هستند.

Backtickها به ما اجازه می‌دهند که یک “تابع الگو” قبل از backtick اول مشخص کنیم. سینتکس اینگونه است: func`string`. تابع func به طور خودکار صدا زده می‌شود، رشته را دریافت می‌کند و عبارات را ایجاد می‌کند و می‌تواند با آنها فرایندی انجام دهد. به این “الگوهای برچسب گذاری شده” می‌گویند. این ویژگی پیاده‌سازی الگوهای سفارشی را آسان‌تر می‌کند، اما در عمل خیلی کم استفاده می‌شود. می‌توانید درباره آن در کتاب راهنما بیشتر بخوانید.

کاراکترهای خاص

اینکه با کوتیشن‌های تکی و دوتایی رشته‌های چند خطی بسازیم، با استفاده از “کاراکتر خط جدید”، که به صورت \n نوشته می‌شود، امکان پذیر است که یک خط جدید را مشخص می‌کند:

let guestList = "مهمان‌ها:\n * John\n * Pete\n * Mary";

alert(guestList); // لیستی چند خطی از مهمان‌ها

برای مثال، این دو خط برابر هستند، فقط به طور متفاوتی نوشته شده‌اند:

let str1 = "Hello\nWorld"; // "ایجاد دو خط با استفاده از "نماد خط جدید

// هاbacktick ایجاد دو خط با استفاده از خط جدید و
let str2 = `Hello
World`;

alert(str1 == str2); // true

کاراکترهای “خاص” دیگر و غیر متداول هم هستند.

لیست کامل آنها:

کاراکتر توضیحات
\n خط جدید
\r Carriage return: به تنهایی استفاده نمی‌شود. فایل‌های متنی ویندوز از ترکیب دو کاراکتر \r\n برای نمایش یک خط جدید استفاده می‌کند.
\n به دلیل‌های تاریخی، بیشتر نرم‌افزارهای ویندوزی \n را هم می‌شناسند.
\', \" کوتیشن‌ها
\\ Backslash
\t Tab
\b, \f, \v Backspace, Form Feed, Vertical Tab – برای سازگاری نگه داشته شده‌اند، امروزه استفاده نمی‌شوند.
\xXX کاراکتر Unicode همراه با Unicode بر پایه 16 (hexadecimal) داده شده، برای مثال '\x7a' برابر است با 'z'.
\uXXXX یک نماد Unicode با کدی بر پایه 16 (hex) XXXX با کدگذاری UTF-16، برای مثال \u009A که یک Unicode برای نماد کپی‌رایت است ©. باید دقیقا 4 رقم hex داشته باشد.
\u{X…XXXXXX} (1 تا 6 کاراکتر hex) یک نماد Unicode با کدگذاری UTF-32 است. بعضی از کاراکترهای کمیاب با دو نماد Unicode کدگذاری می‌شوند و 4 بایت حجم می‌گیرند. به این روش می‌تونیم کدهای طولانی را جایگذاری کنیم.

مثال‌هایی با Unicode:

alert( "\u00A9" ); // ©
alert( "\u{20331}" ); // 佫 ،(طولانی Unicode) یک حرف کمیاب چینی
alert( "\u{1F60D}" ); // 😍 ،(طولانی دیگر Unicode یک) یک نماد صورت خندان

تمام کاراکترهای خاص با یک کاراکتر backslash \ شروع می‌شوند. همچنین به آن “کاراکتر فرار (escape)” هم می‌گویند.

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

برای مثال:

alert( 'I\'m the Walrus!' ); // I'm the Walrus!

همانطور که می‌بینید، باید قبل از کوتیشن داخلی backslash \ بیاریم، وگرنه در غیر این صورت کوتیشن پایان رشته را نمایش می‌دهد.

قطعا فقط کوتیشن‌هایی که با کوتیشن‌های پایانی یکسان هستند باید فراری شوند. پس، به عنوان یک راه حل زیباتر، به جای آن می‌توانیم به کوتیشن‌های دوتایی یا backtickها سوییچ کنیم:

alert( `I'm the Walrus!` ); // I'm the Walrus!

در نظر داشته باشید که backslash \ برای خوانایی درست رشته توسط جاوااسکریپت به کار برده می‌شود، سپس محو می‌شود. رشته‌ای که درون حافظه است \ ندارد. می‌توانید این موضوع را به صراحت در alert مثال بالایی ببینید.

اما اگر نیاز به نمایش یک backslash \ واقعی در بین رشته داشته باشیم چه کار کنیم؟

این کار شدنی است و ما باید آن را دو برابر کنیم مثل \\:

alert( `The backslash: \\` ); // The backslash: \

طول رشته

ویژگی length دارای طول رشته است:

alert( `My\n`.length ); // 3

در نظر داشته باشید که \n یک کاراکتر “خاص” مفرد است، پس طول در واقع 3 است.

length یک ویژگی است

بعضی اوقات افرادی که زمینه‌ای در بعضی زبان‌های برنامه نویسی دیگر دارند اشتباها str.length() را به جای نوشتن str.length صدا می‌زنند. اینگونه کار نمی‌کند.

لطفا در نظر داشته باشید که str.length یک ویژگی عددی است نه یک تابع. نیازی به اضافه کردن پرانتر بعد از آن نیست.

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

برای دریافت یک کاراکتر در موقعیت pos، از براکت‌ها استفاده کنید یا متد str.charAt(pos) را صدا بزنید. اولین کاراکتر از موقعیت صفر شروع می‌شود:

let str = `Hello`;

// اولین کاراکتر
alert( str[0] ); // H
alert( str.charAt(0) ); // H

// آخرین کاراکتر
alert( str[str.length - 1] ); // o

براکت‌ها روش مدرن دریافت کاراکتر هستند، در حالی که charAt بنا به دلایلی مربوط به تاریخچه زبان وجود دارد.

تنها تفاوت میان آنها این است که اگر کاراکتری پیدا نشود، [] مقدار undefined را برمی‌گرداند، و charAt یک رشته خالی را برمی‌گرداند:

let str = `Hello`;

alert( str[1000] ); // undefined
alert( str.charAt(1000) ); // '' (یک رشته خالی)

همچنین ما می‌توانیم با استفاده از for..of برای کاراکترها حلقه بزنیم:

for (let char of "Hello") {
  alert(char); // H,e,l,l,o (و غیره "l" سپس ،"e" سپس ،"H" می‌شود char)
}

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

رشته‌ها در جاوااسکریپت نمی‌توانند تغییر کنند. اینکه یک کاراکتر را تغییر دهیم غیر ممکن است.

بیایید برای نشان دادن اینکه این کار نخواهد کرد امتحانش کنیم:

let str = 'Hi';

str[0] = 'h'; // ارور می‌دهد
alert( str[0] ); // کار نمی‌کند

یک راه حل این است که رشته‌ای کاملا جدید بسازیم و str را به جای رشته‌ی قدیمی برابر با آن قرار دهیم.

برای مثال:

let str = 'Hi';

str = 'h' + str[1]; // رشته را جایگزین می‌کنیم

alert( str ); // hi

در بخش‌های بعدی مثال‌های بیشتری از این خواهیم دید.

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

متدهای toLowerCase() و toUpperCase() بزرگی و کوچکی حروف را تغییر می‌دهند:

alert( 'Interface'.toUpperCase() ); // INTERFACE
alert( 'Interface'.toLowerCase() ); // interface

یا اگر بخواهیم یک کاراکتر را به حرف کوچک آن تبدیل کنیم اینگونه عمل می‌کنیم:

alert( 'Interface'[0].toLowerCase() ); // 'i'

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

چند راه برای گشتن به دنبال یک زیر رشته در یک رشته وجود دارد.

متد str.indexOf

متد اول str.indexOf(substr, pos) است.

این متد به دنبال substr درون str می‌گردد، و از موقعیت pos داده شده شروع می‌کند، و موقعیتی که زیر رشته مورد نظر پیدا شد یا اگر چیزی پیدا نشد -1 را برمی‌گرداند.

برای مثال:

let str = 'Widget with id';

alert( str.indexOf('Widget') ); // 0 ،در شروع رشته پیدا شد 'Widget' چون
alert( str.indexOf('widget') ); // -1 ،چیزی پیدا نشد، جستجو به بزرگی یا کوچکی حروف حساس است

alert( str.indexOf("id") ); // 1 ،(است id دارای ..idget) در موقعیت 1 پیدا شد "id"

پارامتر اختیاری دوم به ما اجازه جستجو از موقعیت داده شده را می‌دهد.

برای مثال، اولین "id" که وجود دارد در موقعیت 1 است. برای پیدا کردن بعدی، بیایید جستجو را از موقعیت 2 شروع کنیم:

let str = 'Widget with id';

alert( str.indexOf('id', 2) ) // 12

اگر ما مشتاق این هستیم که تمام آنها را پیدا کنیم، می‌توانیم indexOf را دورن یک حلقه اجرا کنیم. تمام صدازدن‌های جدید با موقعیتی بعد از موقعیت زیر رشته‌ی پیدا شده قبلی انجام می‌شود:

let str = 'As sly as a fox, as strong as an ox';

let target = 'as'; // بیایید به دنبال آن بگردیم

let pos = 0;
while (true) {
  let foundPos = str.indexOf(target, pos);
  if (foundPos == -1) break;

  alert( `Found at ${foundPos}` );
  pos = foundPos + 1; // جستجو را از موقعیت بعدی ادامه بده
}

الگوریتم یکسان را می‌توان کوتاه‌تر نوشت:

let str = "As sly as a fox, as strong as an ox";
let target = "as";

let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
  alert( pos );
}
متد str.lastIndexOf(substr, position)

یک متد مشابه str.lastIndexOf(substr, position) هم وجود دارد که از انتهای رشته تا آغاز آن جستجو می‌کند.

این متد زیر رشته‌های پیدا شده را با ترتیب برعکس لیست می‌کند.

یک چیز ناخوشایند در رابطه با indexOf در if وجود دارد. ما نمی‌توانیم آن را اینگونه درون if بگذاریم:

let str = "Widget with id";

if (str.indexOf("Widget")) {
    alert("We found it"); // !کار نمی‌کند
}

در مثال بالا alert نمایش نمی‌دهد زیرا str.indexOf("Widget") مقدار 0 را برمی‌کرداند (به این معنی که زیر رشته مورد نظر را در موقعیت آغازین پیدا کرد). درست است، اما if مقدار 0 را با false برابر فرض می‌کند.

بنابراین، ما باید در واقع -1 را بررسی کنیم، به این شکل:

let str = "Widget with id";

if (str.indexOf("Widget") != -1) {
    alert("We found it"); // !حالا کار می‌کند
}

ترفند bitwise NOT

یکی از ترفندهای قدیمی که اینجا استفاده می‌شود عملگر bitwise NOT ~ است. این عملگر عدد را به یک عدد صحیح 32 بیتی تبدیل می‌کند (و بخش اعشاری را در صورت وجود حذف می‌کند) و سپس تمام بیت‌ها را در نمایش دودویی خودش معکوس می‌کند.

در عمل، این به معنی یک چیز ساده است: برای اعداد صحیح 32 بیتی ~n برابر است با -(n+1).

برای مثال:

alert( ~2 ); // -3, the same as -(2+1)
alert( ~1 ); // -2, the same as -(1+1)
alert( ~0 ); // -1, the same as -(0+1)
alert( ~-1 ); // 0, the same as -(-1+1)

همانطور که می‌بینیم، ~n تنها فقط وقتی که n == -1 باشد برابر با صفر است (برای هر عدد صحیح 32 بیتی تخصیص داده شده‌ی n).

بنابراین، آزمایش if ( ~str.indexOf("...") ) فقط زمانی که نتیجه indexOf برابر با -1 نباشد truthy است. به عبارتی دیگر، زمانی که یک زیر رشته پیدا شود.

افراد از آن برای کوتاه کردن بررسی indexOf استفاده می‌کنند:

let str = "Widget";

if (~str.indexOf("Widget")) {
  alert( 'Found it!' ); // کار می‌کند
}

اینکه از ویژگی‌های زبان با روشی که واضح نباشد استفاده کنیم معمولا پیشنهاد نمی‌شود، اما این ترفند مخصوص در کدهای قدیمی بسیار استفاده می‌شود، پس ما باید آن را متوجه شویم.

فقط به یاد داشته باشید: if (~str.indexOf(...)) به صورت «اگر پیدا شد» خوانده می‌شود.

اگر بخواهیم دقیق باشیم، همانطور که اعداد بزرگ هم توسط عملگر ~ 32 بیتی می‌شوند، اعداد دیگری هم وجود دارند که نتیجه 0 را می‌دهند، کوچک‌ترین آنها ~4294967295=0 است. این روش چنین بررسی‌ای را فقط زمانی که رشته آنقدر طولانی نباشد به درستی انجام می‌دهد.

در حال حاضر ما این روش را فقط در کدهای قدیمی می‌بینیم، چون جاوااسکریپت مدرن متد .includes را فراهم کرده است (قسمت پایینی).

متدهای includes، startsWith، endsWith

متد مدرن‌تر str.includes(substr, pos) با وابستگی به اینکه رشته str درون خودش دارای زیر رشته‌ی substr است یا نه مقدار true/false را برمی‌گرداند.

اگر نیاز داشته باشیم که وجود یک زیر رشته را بررسی کنیم، اما به موقعیت آن نیازی نداریم این متد انتخاب مناسبی است:

alert( "Widget with id".includes("Widget") ); // true

alert( "Hello".includes("Bye") ); // false

آرگومان دوم و اختیاری str.includes موقعیتی است که جستجو از آن شروع می‌شود:

alert( "Widget".includes("id") ); // true
alert( "Widget".includes("id", 3) ); // false وجود ندارد پس "id" از موقعیت 3 هیج

متدهای str.startsWith(بررسی شروع شدن رشته با یک زیر رشته) و str.endsWith(بررسی پایان یافتن رشته با یک زیر رشته) دقیقا کاری را که می‌گویند انجام می‌دهند:

alert( "Widget".startsWith("Wid") ); // true شروع می‌شود پس "Wid" با "Widget"
alert( "Widget".endsWith("get") ); // true پایان می‌یابد پس "get" با "Widget"

گرفتن یک زیر رشته

در جاوااسکریپت 3 متد برای گرفتن یک زیر رشته وجود دارد: substring، substr و slice.

str.slice(start [, end])

قسمتی از رشته را از موقعیت start تا end (شامل end نمی‌شود) را برمی‌گرداند.

برای مثال:

let str = "stringify";
alert( str.slice(0, 5) ); // 'strin' :زیر رشته از 0 تا 5 (شامل 5 نمی‌شود)
alert( str.slice(0, 1) ); // 's' :از 0 تا 1، اما شامل 1 نمی‌شود، پس فقط کاراکتری که در 0 است

اگر هیچ آرگومان دومی در کار نباشد، سپس slice تا آخر رشته می‌رود:

let str = "stringify";
alert( str.slice(2) ); // 'ringify' :از موقعیت دوم تا آخر

مقدارهای منفی برای start/end هم ممکن هستند. آنها به این معنی هستند که موقعیت از آخر رشته شمارش می‌شود:

let str = "stringify";

// از موقعیت 4 از سمت راست شروع می‌شود، در موقعیت 1 از سمت راست پایان می‌یابد
alert( str.slice(-4, -1) ); // 'gif'
str.substring(start [, end])

قسمتی از رشته بین start و end را برمی‌گرداند.

این متد تقریبا مشابه با slice است، اما این اجازه را می‌دهد که start بیشتر از end باشد.

برای مثال:

let str = "stringify";

// یکسان هستند substring این دو برای
alert( str.substring(2, 6) ); // "ring"
alert( str.substring(6, 2) ); // "ring"

// ...اینطور نیست slice اما برای
alert( str.slice(2, 6) ); // "ring" (یکسان است)
alert( str.slice(6, 2) ); // "" (یک رشته خالی)

آرگومان‌های منفی (برخلاف slice) پشتیبانی نمی‌شوند، با آنها مانند 0 رفتار می‎شود.

str.substr(start [, length])

قسمتی از رشته از start، تا length (طول) داده شده را برمی‌گرداند.

در تضاد با متدهای قبلی، این متد به ما اجازه می‌دهد که به جای موقعیت پایانی length (طول) را تعیین کنیم:

let str = "stringify";
alert( str.substr(2, 4) ); // 'ring' :از موقعیت دوم 4 کاراکتر را بگیر

اولین آرگومان می‌تواند برای شمارش از آخر، منفی باشد:

let str = "stringify";
alert( str.substr(-4, 2) ); // 'gi' :از موقعیت چهارم 2 کاراکتر را بگیر

بیایید این متدها را برای جلوگیری از هر گمراهی خلاصه کنیم:

متد انتخاب می‌کند… منفی‌ها
slice(start, end) از start تا end (شامل end نمی‌شود) منفی‌ها مجازند
substring(start, end) بین start و end مقدار منفی به معنای 0 است
substr(start, length) از start به تعداد length کاراکتر می‌گیرد start منفی مجاز است
کدام را انتخاب کنیم؟

تمام آنها می‌توانند کار را انجام دهند. به طور رسمی، substr یک اشکال جزئی دارد: این متد در هسته مشخصات جاوااسکریپت تعریف نشده است، اما در Annex B تعریف شده، که فقط ویژگی‌های مختص به مرورگر را پوشش می‌دهد که به دلایلی مربوط به تاریخچه زبان وجود دارد. پس محیط‌هایی که مرورگر نباشند ممکن است از آن پشتیبانی نکنند. اما در عمل این متد همه‌جا کار می‌کند.

از بین دو متد دیگر، slice مقداری قابل انعطاف‌تر است، و آرگومان‌های منفی را مجاز می‌داند و برای نوشتن کوتاه‌تر است. پس فقط به یاد داشتن slice از بین این سه متد کافی است.

مقایسه رشته‌ها

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

گرچه، جزییاتی وجود دارد.

  1. یک حرف کوچک انگلیسی همیشه از حرف بزرگ، بزرگتر است:

    alert( 'a' > 'Z' ); // true
  2. حروفی که علامت دارند “بدون ترتیب” هستند:

    alert( 'Österreich' > 'Zealand' ); // true

    اگر ما اسم این کشورها را مرتب کنیم این موضوع ممکن است باعث ایجاد نتایج عجیب شود. معمولا مردم توقع داشند که Zealand بعد از Österreich در لیست بیاید.

برای فهمیدن اینکه چه چیزی رخ می‌دهد، بیایید نمایش داخلی رشته‌ها را در جاوااسکریپت مرور کنیم.

تمام رشته‌ها با استفاده از UTF-16 کدگذاری شده‌اند. یعنی اینکه: هر کاراکتر یک کد عددی متناظر دارد. متدهای خاصی هستند که گرفتن کد از کاراکتر و برعکس را ممکن می‌سازند.

str.codePointAt(pos)

کد کاراکتر را در موقعیت pos برمی‌گرداند:

// حروف با بزرگی یا کوچکی متفاوت کدهای متفاوت دارند
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
String.fromCodePoint(code)

یک کاراکتر با استفاده از کد عددی آن می‌سازد:

alert( String.fromCodePoint(90) ); // Z

همچنین ما می‌توانیم کاراکترهای Unicode را از طریق کد آنها با استفاده از \u که بعد از آن کد hex می‌آید اضافه کنیم:

// 5a عدد 90 در سیستم عددی بر پایه 16 برابر است با
alert( '\u005a' ); // Z

حال بیایید با ساختن یک رشته از کاراکترهایی که کد 65..220 دارند آنها را نگاه بیاندازیم (حروف الفبای لاتین و کمی بیشتر):

let str = '';

for (let i = 65; i <= 220; i++) {
  str += String.fromCodePoint(i);
}
alert( str );
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„
// ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜ

می‌بینید؟ کاراکترهای حروف بزرگ اول هستند، سپس چند حرف خاص، سپس کارکترهای حروف کوچک، و Ö نزدیک به پایان خروجی است.

حالا واضح شد که چرا a > Z.

کاراکترها از طریق کدهای عددی خود مقایسه می‌شوند. کد بزرگتر به معنای بزرگتر بودن کاراکتر است. کد a (97) بزرگتر از کد Z (90) است.

  • تمام حروف کوچک انگلیسی بعد از حروف بزرگ واقع هستند چون کدهای آنها بزرگتر هستند.
  • بعضی از حروف مانند Ö از حروف الفبای اصلی جدا هستند. اینجا، کد آن از هر چیزی بین a تا z بزرگتر است.

مقایسه‌های صحیح

الگوریتم “درست” برای انجام مقایسه رشته‌ها پیچیده‌تر از چیزی است که بنظر می‌آید، چون الفبا برای زبان‌های مختلف متفاوت است.

پس، مرورگر نیاز دارد که زبان را برای مقایسه کردن بداند.

خوشبختانه، تمام مرورگرهای مدرن (IE10 به کتابخانه اضافی Intl.js احتیاج دارد) از استاندارد بین‌المللی‌کردن ECMA-402 پشتیبانی می‌کنند.

این استاندارد یک متد خاص را برای مقایسه رشته‌ها در زبان‌های مختلف را مهیا می‌کند که از قوانین خودشان پیروی می‌شود.

صدازدن str.localeCompare(str2) یک عدد صحیح را برمی‌گرداند که نشان می‌دهد آیا str با توجه به قوانین زبان، کمتر، مساوی یا برابر از str2 هست یا نه:

  • اگر str کمتر از str2 باشد یک عدد منفی برمی‌گرداند.
  • اگر str بزرگتر از str2 باشد یک عدد مثبت برمی‌گرداند.
  • اگر آنها برابر باشند 0 را برمی‌گرداند.

برای مثال:

alert( 'Österreich'.localeCompare('Zealand') ); // -1

این متد دو آرگومان اضافی دارد که در مستندات مشخص شده‌اند که به ما اجازه می‌دهند تا زبان را مشخص کنیم (به طور پیش‌فرض از شرایط فعلی بدست می‌آید، ترتیب حروف به زبان بستگی دارد) و قوانین اضافی را ایجاد کنیم مثل حساسیت بزرگی یا کوچکی حرف یا اینکه به یک صورت با "a" و "á" رفتار شود و غیره.

داخلی‌ها، Unicode

اطلاعات پیشرفته

این بخش بیشتر درون رشته‌ها پیش می‌رود. این اطلاعات در صورتی که شما قصد داشته باشید با اموجی، کاراکترهای تصویری نادر ریاضی یا نشانه‌های نادر دیگر کار کنید برای شما مفید خواهد بود.

اگر قصد فرا گرفتن آنها را ندارید می‌توانید از این بخش بگذرید.

جفت‌های جایگیر

تمام کاراکترهایی که اکثر اوقات استفاده می‌شوند کدهای 2 بایتی دارند. حروف در اکثر زبان‌های اروپایی، اعداد، و حتی اکثر حروف تصویری، یک نمایش 2 بایتی دارند.

اما 2 بایت فقط 65536 ترکیب را ممکن می‌سازد و این مقدار برای هر نشانه موجود کافی نیست. پس نشانه‌های نادر با جفتی از کاراکترهای 2 بایتی که “جفت جایگیر” (surrogate pair) هم نامیده می‌شوند کدگذاری می‌شوند.

طول چنین نشانه‌هایی 2 است:

alert( '𝒳'.length ); // 2، X اسکریپت ریاضی حرف بزرگ
alert( '😂'.length ); // 2، صورت با اشک شوق
alert( '𩷶'.length ); // 2، یک حرف تصویری نادر چینی

در نظر داشته باشید که جفت‌های جایگیر زمانی که جاوااسکریپت ساخته شد وجود نداشتند، و به این دلیل در حال حاضر توسط زبان به درستی پردازش نمی‌شوند!

ما در واقع در هر یک از رشته‌های بالا یک نشانه مفرد داریم، اما length طول 2 را نشان می‌دهد.

String.fromCodePoint و str.codePointAt دو متد نادر هستند که با جفت‌های جایگیر به درستی کار می‌کنند. آنها اخیرا به زبان اضافه شدند. قبل آنها، فقط String.fromCharCode و str.charCodeAt وجود داشتند. این متدها در واقع با fromCodePoint/codePointAt یکی هستند، اما با جفت‌های جایگیر کار نمی‌کنند.

گرفتن یک نشانه می‌تواند آسان نباشد، چون با جفت‌های جایگیر مثل دو کاراکتر رفتار می‌شود:

alert( '𝒳'[0] ); // ...نشانه‌های عجیب
alert( '𝒳'[1] ); // قطعه‌هایی از جفت جایگیر...

توجه کنید که قطعه‌های جفت جایگیر بدون یکدیگر هیچ معنی‌ای ندارند. پس alertها در مثال بالا در واقع چیزهای بدرد نخور نمایش می‌دهند.

به طور فنی، جفت‌های جایگیر هم با کدهای خود قابل شناسایی هستند: اگر یک کاراکتر کدی در فاصله 0xd800..0xdbff داشته باشد، پس قطعه اول یک جفت جایگیر است. کاراکتر بعدی (قطعه دوم) باید کدی در فاصله 0xdc00..0xdfff داشته باشد. این بازه‌ها به طور اختصاصی برای جفت‌های جایگیر رزرو شده‌اند.

در مورد بالایی:

// جفت‌های جایگیر را نمی‌شناسد، پس کدهای قطعه‌ها را به ما می‌دهد charCodeAt

alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff
alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff

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

نشانه‌های تفکیک کننده و عادی‌سازی

در بسیاری از زبان‌ها نشانه‌هایی وجود دارند که ترکیبی از یک کاراکتر پایه و یک علامت در بالا/پایین آن هستند.

برای مثال، حرف a حرف پایه برای àáâäãåā است. اکثر کاراکترهای «ترکیب‌شده» متداول کد خود را در جدول UTF-16 دارند. اما نه همه آنها، چون ترکیبات ممکن بسیار زیادی وجود دارد.

برای پشتیبانی از ترکیبات دلخواه، UTF-16 به ما اجازه می‌دهد که از چند کاراکتر Unicode استفاده کنیم: کاراکتر پایه که بعد از آن یک یا چند کاراکتر «علامت» می‌آید که آن را «زیبا می‌کند».

برای مثال، اگر ما یک حرف S داشته باشیم که بعد از آن کاراکتر خاص «نقطه بالا» آمده باشد (کد \u0307)، به صورت Ṡ نمایش داده می‌شود.

alert( 'S\u0307' ); // Ṡ

اگر ما نیاز به یک علامت اضافی در بالای حرف داشته باشیم (یا پایین آن)، مشکلی نیست، فقط کاراکتر علامت مورد نیاز را اضافه می‌کنیم.

برای مثال، اگر ما یک کاراکتر «نقطه پایین» (کد \u0323) را ضمیمه کنیم، سپس ما «حرف S با نقطه‌هایی در بالا و پایین آن» خوهیم داشت: .

برای مثال:

alert( 'S\u0307\u0323' ); // Ṩ

این روش انعطاف زیادی را مهیا می‌کند، اما یک مشکل جالب هم دارد: دو کاراکتر ممکن است که ظاهر یکسانی داشته باشند، اما با ترکیبات Unicode متفاوت نمایش داده شوند.

برای مثال:

let s1 = 'S\u0307\u0323'; // Ṩ ،نقطه بالا + نقطه پایین + S
let s2 = 'S\u0323\u0307'; // Ṩ ،نقطه پایین + نقطه بالا + S

alert( `s1: ${s1}, s2: ${s2}` );

alert( s1 == s2 ); // (!؟)می‌شود false با اینکه کاراکترها مشابه هستند اما

برای رفع این مشکل، یک الگوریتم «عادی‌سازی Unicode» وجود دارد که هر رشته را به یک شکل «عادی» درمی‌آورد.

این الگوریتم با str.normalize() پیاده‌سازی می‌شود.

alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true

موضوع بامزه‌ای است که در این مورد ما normalize() در واقع یک دنباله از 3 کاراکتر را به یک کاراکتر تبدیل می‌کند: \u1e68 (S به همراه دو نقطه).

alert( "S\u0307\u0323".normalize().length ); // 1

alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true

در واقعیت، همیشه این مورد پیش نمی‌آید. به دلیل اینکه نماد «به اندازه کافی متداول» است، پس سازندگان UTF-16 آن را در جدول اصلی آوردند و به آن یک کد دادند.

اگر شما می‌خواهید درباره قوانین و انواع عادی‌سازی بدانید، آنها در ضمیمه استاندارد Unicode تعریف شده‌اند: شکل‌های عادی‌سازی Unicode، اما برای اکثر کارهای عملی و کاربردی اطلاعات این بخش کافی است.

خلاصه

  • 3 نوع کوتیشن وجود دارد. Backtickها به ما این امکان را می‌دهند که رشته را به چند خط تقسیم کنیم و عبارت‌هایی را درون رشته جایگذاری کنیم ${…}.
  • رشته‌ها در جاوااسکریپت با استفاده از UTF-16 کدگذاری شده‌اند.
  • ما می‌توانیم از کاراکترهای خاص مانند \n استفاده کنیم و حروف را از طریق کد Unicode آنها با استفاده از \u... بنویسیم.
  • برای گرفتن یک کاراکتر، از [] استفاده کنید.
  • برای گرفتن یک زیر رشته از slice یا substring استفاده کنید.
  • برای تغییر بزرگی یا کوچکی حروف انگلیسی یک رشته، از toLowerCase/toUpperCase استفاده کنید.
  • برای گشتن به دنبال یک زیر رشته از indexOf یا برای بررسی‌های ساده از includes/startsWith/endsWith استفاده کنید.
  • برای مقایسه رشته‌ها با توجه به زبان آنها، از localeCompare استفاده کنید، در غیر این صورت آنها توسط کدهای کاراکتر مقایسه می‌شوند.

چند متد دیگر هم برای رشته‌ها وجود دارد:

  • str.trim() – فاصله را از ابتدا و انتهای رشته حذف می‌کند («می‌تراشد»).
  • str.repeat(n) – رشته را n بار تکرار می‌کند.
  • …و متدهای بیشتری در مستندات وجود دارند.

رشته‌ها متدهایی را برای جستجو/جایگزین‌کردن عبارات با قاعده (regular expression) دارند. اما این یک بحث بزرگ است، پس در یک قسمت جدای این آموزش عبارات باقاعده (Regular Expression) توضیح داده شده است.

تمارین

اهمیت: 5

یک تابع ucFirst(str) بنویسید که رشته str را با حرف اول بزرگ شده برمی‌گرداند، برای مثال:

ucFirst("john") == "John";

باز کردن یک sandbox همراه با تست‌ها.

ما نمی‌توانیم حرف اول را «جایگزین» کنیم، چون رشته‌ها در جاوااسکریپت غیر قابل تغییر هستند.

اما می‌توانیم یک رشته جدید را بر اساس رشته موجود با کاراکتر اول بزرگ شده بسازیم:

let newStr = str[0].toUpperCase() + str.slice(1);

البته یک مشکل کوچک وجود دارد. اگر str خالی باشد، پس str[0] برابر با undefined است و undefined متد toUpperCase() را ندارد، پس ما ارور خواهیم داشت.

دو روش پیش روی‌مان داریم:

  1. از str.charAt(0) استفاده کنیم، چون همیشه یک رشته برمی‌گرداند (شاید رشته خالی).
  2. یک تست برای رشته خالی اضافه کنیم.

روش دوم را اینجا داریم:

function ucFirst(str) {
  if (!str) return str;

  return str[0].toUpperCase() + str.slice(1);
}

alert( ucFirst("john") ); // John

باز کردن راه‌حل همراه با تست‌ها درون یک sandbox.

اهمیت: 5

یک تابع checkSpam(str) بنویسید که اگر str دارای کلمات ‘viagra’ یا ‘XXX’ باشد مقدار true را برگرداند، در غیر این صورت false.

تابع نباید به بزرگی یا کوچکی حرف حساس باشد:

checkSpam('buy ViAgRA now') == true
checkSpam('free xxxxx') == true
checkSpam("innocent rabbit") == false

باز کردن یک sandbox همراه با تست‌ها.

برای اینکه جستجو را به بزرگی یا کوچکی حرف حساس نکنیم، بیایید حروف رشته را کوچک کنیم و سپس جستجو کنیم:

function checkSpam(str) {
  let lowerStr = str.toLowerCase();

  return lowerStr.includes('viagra') || lowerStr.includes('xxx');
}

alert( checkSpam('buy ViAgRA now') );
alert( checkSpam('free xxxxx') );
alert( checkSpam("innocent rabbit") );

باز کردن راه‌حل همراه با تست‌ها درون یک sandbox.

اهمیت: 5

یک تابع truncate(str, maxlength) بسازید که طول str را بررسی می‌کند و اگر از maxlength بیشتر باشد، پایان رشته str را با کاراکتر حذف "…" جایگذاری کند، تا طول آن برابر با maxlength شود.

نتیجه تابع باید رشته کوتاه‌شده باشد (در سورت نیاز).

برای مثال:

truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…"

truncate("Hi everyone!", 20) = "Hi everyone!"

باز کردن یک sandbox همراه با تست‌ها.

بیشترین طول باید maxlength باشد، پس ما نیاز داریم که آن را کمتر کنیم، تا برای کاراکتر حذف جا باز شود.

در نظر داشته باشید در واقع یک کاراکتر Unicode برای کاراکتر حذف وجود دارد. این کاراکتر سه نقطه نیست.

function truncate(str, maxlength) {
  return (str.length > maxlength) ?
    str.slice(0, maxlength - 1) + '…' : str;
}
function truncate(str, maxlength) {
  return (str.length > maxlength) ?
    str.slice(0, maxlength - 1) + '…' : str;
}

باز کردن راه‌حل همراه با تست‌ها درون یک sandbox.

اهمیت: 4

ما یک قیمت به شکل "$120" داریم. به این معنی که علامت دلار اول می‌آید، و سپس عدد.

یک تابع extractCurrencyValue(str) بسازید که مقدار عددی را از چنین رشته‌ای بیرون می‌کند و آن را برمی‌گرداند.

مثال:

alert( extractCurrencyValue('$120') === 120 ); // true

باز کردن یک sandbox همراه با تست‌ها.

function extractCurrencyValue(str) {
  return +str.slice(1);
}
function extractCurrencyValue(str) {
  return +str.slice(1);
}

باز کردن راه‌حل همراه با تست‌ها درون یک sandbox.

نقشه آموزش

نظرات

قبل از نظر دادن این را بخوانید…
  • اگر پیشنهادی برای بهبود ترجمه دارید - لطفا یک ایشوی گیت‌هاب یا یک پول‌ریکوئست به جای کامنت‌گذاشتن باز کنید.
  • اگر چیزی را در مقاله متوجه نمی‌شوید – به دقت توضیح دهید.
  • برای قراردادن یک خط از کد، از تگ <code> استفاده کنید، برای چندین خط – کد را درون تگ <pre> قرار دهید، برای بیش از ده خط کد – از یک جعبهٔ شنی استفاده کنید. (plnkr، jsbin، codepen…)