۱۴ نوامبر ۲۰۲۲

رشته‌ها

در جاوااسکریپت، داده‌ی متنی به عنوان رشته (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

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

There are other, less common special characters:

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

کاراکتر توضیحات

<<<<<<< HEAD |\n|خط جدید| |\r|فایل‌های متنی ویندوز از ترکیب دو کاراکتر \r\n برای نمایش یک خط جدید استفاده می‌کند، در حالی که برای سیستم‌های غیر ویندوزی \n این کار را انجام می‌دهد. دلیل آن مربوط به گذشته‌ها است. بیشتر نرم‌افزارهای ویندوزی \n را هم می‌شناسند. | |\'\"\</code>|کوتیشن‌ها| |\|Backslash| |\t|Tab| |\b,\f,\v`| Backspace, Form Feed, Vertical Tab – برای کامل بودن مطالب گفته شده‌اند، از قدیم وجود دارند، امروزه استفاده نمی‌شوند (شما می‌توانید همین حالا آن‌ها را فراموش کنید).|

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

چون این کاراکتر خاص است، اگر بخواهیم یک backslash \ واقعی درون رشته نشان دهیم، باید آن را دوبل کنیم:

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

کوتیشن‌های «escaped» \'، \"، \` برای اضافه کردن یک کوتیشن به رشته‌ای که در همان نوع کوتیشن قرار گرفته است استفاده می‌شوند.

برای مثال:

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

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

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

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

<<<<<<< HEAD در کنار این کاراکترهای خاص، همچنین یک نشان خاص برای کدهای Unicode \u… وجود دارد که کمی بعدتر در این فصل آن را پوشش می‌دهیم.

Besides these special characters, there’s also a special notation for Unicode codes \u…, it’s rarely used and is covered in the optional chapter about Unicode.

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

طول رشته

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

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

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

length یک ویژگی است

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

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

Please note that str.length is a numeric property, not a function. There is no need to add parenthesis after it. Not .length(), but .length.

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

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

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

To get a character at position pos, use square brackets [pos] or call the method str.at(pos). The first character starts from the zero position:

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

let str = `Hello`;

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

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

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

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

As you can see, the .at(pos) method has a benefit of allowing negative position. If pos is negative, then it’s counted from the end of the string.

So .at(-1) means the last character, and .at(-2) is the one before it, etc.

The square brackets always return undefined for negative indexes, for instance:

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

let str = `Hello`;

<<<<<<< HEAD
alert( str[1000] ); // undefined
alert( str.charAt(1000) ); // '' (یک رشته خالی)
=======
alert( str[-2] ); // undefined
alert( str.at(-2) ); // l
>>>>>>> 18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

همچنین ما می‌توانیم با استفاده از 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"); // !حالا کار می‌کند
}

متدهای 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 را برمی‌گرداند (شامل end نمی‌شود).

این متد تقریبا مشابه با slice است، اما این اجازه را می‌دهد که start بیشتر از end باشد (در این صورت مقدارهای 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 (شامل 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 در لیست بیاید.

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

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

To understand what happens, we should be aware that strings in Javascript are encoded using UTF-16. That is: each character has a corresponding numeric code.

There are special methods that allow to get the character for the code and back:

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

str.codePointAt(pos)

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

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

<<<<<<< HEAD alert( “Z”.codePointAt(0) ); // 90 alert( “z”.codePointAt(0).toString(16) ); // 7a (اگر ما به مقدار هگزادسیمال کد نیاز داشته باشیم)

alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value)

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

```
String.fromCodePoint(code)

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

alert( String.fromCodePoint(90) ); // Z
alert( String.fromCodePoint(0x5a) ); // Z (همچنین می‌توانیم از یک مقدار هگزادسیمال به عنوان آرگومان استفاده کنیم)

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

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

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

Now let’s see the characters with codes 65..220 (the latin alphabet and a little bit extra) by making a string of them:

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

let str = '';

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

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

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

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

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

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

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

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

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

Luckily, modern browsers support the internationalization standard ECMA-402.

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

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

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

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

برای مثال:

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

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

<<<<<<< HEAD

داخلی‌ها، Unicode

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

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

کاراکترهای Unicode

همانطور که قبلا هم گفتیم، رشته‌های جاوااسکریپت بر اساس Unicode است.

هر کاراکتر توسط یک دنباله از 1-4 بایت نشسان داده می‌شود.

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

  • \xXX – یک کاراکتر که کد Unicode آن U+00XX است.

    XX دو رقم هگزادسیمال با مقداری بین 00 و FF است پس نشانه \xXX فقط می‌تواند برای 256 کاراکتر اول Unicode استفاده شود (شامل تمام 128 ASCII کاراکتر می‌شود).

    این 256 کاراکتر اول شامل حروف الفبای لاتین، اکثر کاراکترهای سینتکس پایه و دیگر موارد می‌شوند. برای مثال، "\x7A" با "z" یکسان است (Unicode U+007A).

  • /uXXXX – کاراکتری که کد Unicode آن U+XXXX است (یک کاراکتر با کد هگزادسیمال XXXX به صورت UTF-16 کدگذاری شده است).

    XXXX باید دقیقا 4 رقم و با مقداری بین 0000 و FFFF باشد پس نشانه \uXXXX می‌تواند برای 65536 کاراکتر اول استفاده شود. کاراکترهایی با مقدار Unicode بیشتر از U+FFFF هم می‌توانند با این نشانه نمایش داده شوند اما در این صورت ما باید از چیزی به اصطلاح surrogate pair (ترجمه: جفت جایگیر) استفاده کنیم (درباره این جفت کمی بعدتر در این مقاله صحبت می‌کنیم).

  • u{X…XXXXXX} – کاراکتری با هر کد Unicode (یک کاراکتر با کد هگزادسیمال که به صورت UTF-32 کدگذاری شده است).

    X…XXXXXX باید مقداری هگزادسیمال با 1 تا 6 بایت بین 0 و 10FFFF باشد (بزرگترین کد تعریف شده توسط Unicode). این نشانه به ما اجازه می‌دهد تا به سادگی تمام کاراکترهای Unicode موجود را نمایش دهیم.

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

alert( "\uA9" ); // © ،علامت کپی‌رایت

alert( "\u00A9" ); // © ،مانند بالا، با استفاده از نشانه هگزادسیمال 4 رقمی
alert( "\u044F" ); // я ،cyrillic حرف الفبای
alert( "\u2191" ); // ↑, نماد کمان رو به بالا

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

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

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

در ابتدا، جاوااسکریپت بر اساس کدگذاری UTF-16 بود که فقط 2 بایت به ازای هر کاراکتر را ممکن می‌ساخت. اما 2 بایت فقط 65536 ترکیب را ممکن می‌سازد و این مقدار برای هر نشانه موجود کافی نیست.

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

به عنوان یک عارضه جانبی، طول چنین نشانه‌هایی 2 است:

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

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

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

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

برای مثال، ما اینجا دو کاراکتر عجیب را در خروجی می‌توانیم ببینیم:

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

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

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

پس متدهای String.fromCodePoint و str.codePointAt برای کار کردن با جفت‌های جایگیر به جاوااسکریپت اضافه شدند.

آن‌ها اساسا با String.fromCharCode و str.charCodeAt یکسان هستند اما با جفت‌های جایگیر به درستی رفتار می‌کنند.

می‌توانید تفاوت را اینجا ببینید:

// را نتیجه می‌دهد 𝒳 جفت‌های جایگیر را نمی‌ناسد پس کد اولین قسمت charCodeAt

alert( '𝒳'.charCodeAt(0).toString(16) ); // d835

// جفت‌های جایگیر را می‌شناسد codePointAt
alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3 ،هر دو قسمت جفت جایگیر را می‌خواند

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

alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3
alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3
// قسمت دوم بی‌معنای جفت

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

نتیجه: تقسیم کردن یک رشته در نقطه‌ای دلخواه خطرناک است

We can’t just split a string at an arbitrary position, e.g. take str.slice(0, 4) and expect it to be a valid string, e.g.: ما نمی‌توانیم یک رشته را در یک نقطه دلخواه جدا کنیم مثلا str.slic(0, 4) را در نظر بگیریم و تقوع یک رشته معتبر را داشته باشیم، مثلا:

alert( 'hi 😂'.slice(0, 4) ); //  hi [?]

اینجا می‌توانیم یک کاراکتر بدرد نخور را در خروجی ببینیم (اولین قسمت از جفت جایگیر علامت خنده).

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

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

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

برای مثال، حرف 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

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

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

خلاصه

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

Summary

  • There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions ${…}.
  • We can use special characters, such as a line break \n.
  • To get a character, use: [] or at method.
  • To get a substring, use: slice or substring.
  • To lowercase/uppercase a string, use: toLowerCase/toUpperCase.
  • To look for a substring, use: indexOf, or includes/startsWith/endsWith for simple checks.
  • To compare strings according to the language, use: localeCompare, otherwise they are compared by character codes.

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e

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

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

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

Strings also have methods for doing search/replace with regular expressions. But that’s big topic, so it’s explained in a separate tutorial section عبارات باقاعده (Regular Expression).

Also, as of now it’s important to know that strings are based on Unicode encoding, and hence there’re issues with comparisons. There’s more about Unicode in the chapter یونی‌کد، درون رشته‌ها. <<<<<<< HEAD

18b1314af4e0ead5a2b10bb4bacd24cecbb3f18e =======

8d9ecb724c7df59774d1e5ffb5e5167740b7d321

تمارین

اهمیت: 5

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

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

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

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

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

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

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

آسان‌ترین روش اضافه کردن یک تست برای یک رشته خالی است، مانند:

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.

نقشه آموزش