مقدمه
قبلا تو مقاله ی ” ورژن بندی اکما اسکریپت (جاوا اسکریپت) ” از مجله آموزشی ویکس سون، امکانات و فیچرهایی که تو Es11 به جاوا اسکریپت اضافه شده رو معرفی کردیم. در ادامه به جزییات این قابلیت ها و آموزششون میپردازیم.
امکانات و قابلیت های جاوا اسکریپت Es11 (2020)
- عددهای بزرگ یا BigInt
- متد String.prototype.matchAll در استرینگ (رشته) ها
- متد Promise.allSettled در پرامیس
- آبجکت globalThis
- قابلیت Dynamic Import
- عملگر نالیش ?? یا nullish coalescing operator
- عملگر زنجیره اختیاری .? یا optional chaining operator
- عملگر نسبت دهی منطقی اند =&& یا logical AND assignment operator
- عملگر نسبت دهی منطقی اور =|| یا logical AND assignment operator
- عملگر نسبت دهی نالیش =?? یا nullish coalescing assignment operator
آموزش کامل جاوا اسکریپت Es11 (2020)
(با توجه به اینکه این ورژن، ورژن نسبتا جدیدی به حساب میاد، با احتیاط از فیچرهاش استفاده کنید)
پیشنیاز این آموزش :
- آشنایی با مفاهیم پایه جاوا اسکریپت
- آشنایی با قابلیت های جدید جاوا اسکریپت تا اکما اسکریپت 2019 یا Es10
1) عددهای بزرگ یا BigInt در جاوا اسکریپت Es11
تا قبل از Es11، بزرگترین عدد صحیحی که جاوا اسکریپت پشتیبانی میکرد، عدد 9007199254740991 بود که اگه دستور زیر رو بنویسیم توی کنسول به ما نمایش داده میشه :
console.log(Number.MAX_SAFE_INTEGER); // output: 9007199254740991
اما اگه عدد بزرگتری وارد کنیم (بیشتر از 16 رقم)، مرورگر قادر به پردازش این عدد نیست و خروجی درستی رو نمایش نمیده. مثلا عدد زیر تو مرورگر گوگل کروم برای من خروجی که روبروش نوشتم رو نمایش میده :
console.log(1234567890123456789012345); // output: 1.2345678901234568e+24
به همین خاطر بود که سال 2020، نوع داده جدیدی به نام BigInt معرفی شد که به کمکش میتونیم اعداد بزرگ (بیشتر از 16 رقم) رو تعریف کنیم. برای اینکار میتونیم از 2 روش استفاده کنیم :
-
- موقع تعریف متغیر عددی، آخر عدد، حرف n رو قرار بدیم
- از متد BigInt() برای تعریف متغیر عددی استفاده کنیم
تو کد زیر از هر دو روش برای تعریف عدد بزرگی که بالاتر نوشته بودیم استفاده کردیم :
let bigInt1 = 1234567890123456789012345n;
let bigInt2 = BigInt(1234567890123456789012345);
console.log(bigInt1); // output: 1234567890123456789012345n
نکته : BigInt یه نوع دیتاتایپ جدید و مستقل هستش و جزء number به حساب نمیاد. اگه از دستور typeof برای این اعداد استفاده کنیم، bigint نمایش داده میشه (نه number)
2) متد String.prototype.matchAll در استرینگ (رشته) های جاوا اسکریپت Es11
برای اینکه بتونیم متد matchAll رو بررسی کنیم باید متد match رو خوب بشناسیم. بنا رو بر این میذاریم که با رجکس و نحوه ی استفاده ش تو جاوا اسکریپت آشنا هستین. همونطور که میدونید به کمک متد match میتونستیم یه الگوی رجکسی رو توی متنمون جستجو کنیم و اگه مطابقتی وجود داشت، این متد برامون اولین نتیجه رو برمیگردونه. و اگه میخواستیم که همه ی نتایج رو برگردونه (نه فقط اولی) باید از g flag تو رجکسمون استفاده میکردیم. مثلا تو کد زیر تو حالت اول بدون g flag تونستیم فقط نتیجه ی اول رو برگردونیم و تو حالت دوم با g flag تونستیم هر دو نتیجه رو برگردونیم :
const text = "I have two cats. My cats are so cute";
const result = text.match(/cats/);
console.log(result); // output: ['cats', index: 11, input: 'I have two cats. My cats are so cute', groups: undefined]
const results = text.match(/cats/g);
console.log(results);
/* output:
(2) ['cats', 'cats']
0 : "cats"
1 : "cats"
*/
خب همونطور که دیدین، این متد برامون تو حالت اول یه آرایه شامل اطلاعات دقیقی از کلمه رو برامون برگردوند، اما تو حالت دوم فقط یه آرایه که دو عضو داره برگردوند و اطلاعات دقیقی از این دو عضو ارائه نمیده. اینجاست که متد matchAll کاربردشو نشون میده! در واقع ما به کمک matchAll کاری میکنیم که خروجی نهاییمون مثل حالت اول متد match اطلاعات دقیقی از هر عضو بهمون بده. متد matchAll رو برای کد بالا به صورت زیر مینویسیم :
const iterator = text.matchAll(/cats/g);
console.log(iterator); // output: RegExpStringIterator {}
همونطور که تو کد بالا میبینید، این متد به جای آرایه برامون یه آبجکت قابل پیمایش برگردونده، پس برای دسترسی به هر آیتمش 2 راه داریم :
-
- استفاده از حلقه for-of
- استفاده از متد Array.from
در ادامه ی کد بالا، تو کدهای زیر از هر دو روش استفاده کردیم تا خروجی هر دو حالت رو ببینیم :
for (const item of iterator) {
console.log(item);
}
/* output:
['cats', index: 11, input: 'I have two cats. My cats are so cute', groups: undefined]
['cats', index: 20, input: 'I have two cats. My cats are so cute', groups: undefined]
*/
const array = Array.from(iterator);
console.log(array);
/* output :
(2) [Array(1), Array(1)]
0 : ['cats', index: 11, input: 'I have two cats. My cats are so cute', groups: undefined]
1 : ['cats', index: 20, input: 'I have two cats. My cats are so cute', groups: undefined]
*/
همونطور که انتظار داشتیم در هر دو حالت تونستیم به نتایج مورد نظرمون به همراه اطلاعات کامل و دقیقشون (در قالب آرایه) دست پیدا کنیم.
نکته : اگه الگوی رجکسی مورد نظرمون توی متن پیدا نشه، خروجی متد match، برابر با null و خروجی متد matchAll یه آبجکت قابل پیمایش خالی هست. نکته : متد replaceAll تو جاوا اسکریپت Es12 معرفی شده که تو مقاله مربوط به خودش بهش میپردازیم.
3) متد Promise.allSettled در پرامیس های جاوا اسکریپت Es11
همونطور که میدونید، پرامیس ها تو Es6 برای مدیریت بهتر و راحت تر پردازش های ناهمگام (async) معرفی شدن. و همونطور که باز هم میدونید، پرامیس ها یه سری متد با عنوان Concurrency (ترجمه فارسی: همزمانی) دارن که به کمکشون میتونیم چند پرامیس رو همزمان مدیریت کنیم. این متدها 4 تا هستن :
-
- Promise.all : اگه همه ی پرامیس ها fulfill بشن، fulfill میشه
- Promise.race : اگه یکی از پرامیس ها پاسخ دریافت کنه، پاسخ دریافت میکنه
- Promise.allSettled : اگه همه ی پرامیس ها پاسخ دریافت کنن، fulfill میشه
- Promise.any : اگه حداقل یکی از پرامیس ها fulfill بشه، fulfill میشه
نکته : منظور از “پاسخ دریافت کردن” اینه که پرامیس یا reject بشه یا fulfill (یعنی پرامیس pending نباشه)
دو مورد اول قبلا تو Es6 ارائه شدن و باهاشون آشنا هستیم. اما Promise.allSettled تو Es11 معرفی شده که اینجا بهش میپردازیم (Promise.any هم تو Es12 معرفی شده که توی مقاله ی ” جاوا اسکریپت Es12 ” بهش پرداختیم)
خب! Promise.allSettled مثل بقیه متد های Promise Concurrency، ورودیش یه دیتای قابل پیمایش از پرامیس هاست (مثلا آراایه ای از پرامیس ها) و خروجیش هم یه تک پرامیسه که :
-
- اگه همه ی پرامیس هاش پاسخ دریافت کنن (sattle)، fulfill میشه
- خروجیش یه آرایه از تعدادی آبجکته که هر آبجکت حاوی اطلاعات مربوط به یکی از پرامیس هاست که این اطلاعات شامل موارد زیر هست :
-
-
- status : وضعیت پرامیس رو نمایش میده ( fulfill و reject )
- value : فقط در صورتی که پرامیس fulfill بشه نمایش داده میشه
- reason : فقط در صورتی که پرامیس reject بشه نمایش داده میشه
-
به عنوان مثال تو کد زیر 2 تا پرامیس (که یکی reject شده و یکی fulfill)، به عنوان ورودی به Promise.allSettled پاس دادم و اولین بار خود Promise.allSettled و دومین بار خروجیش رو لاگ گرفتم که مواردی که توضیح دادم رو توی کد ببینید:
const promise1 = new Promise((res, rej) => setTimeout(res, 2000));
const promise2 = new Promise((res, rej) => setTimeout(rej, 2000));
console.log(Promise.allSettled([promise1, promise2]));
/* output:
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(2)
*/
Promise.allSettled([promise1, promise2]).then((data) => console.log(data));
/* output:
(2) [{…}, {…}]
0: {status: 'fulfilled', value: undefined}
1: {status: 'rejected', reason: undefined}
*/
4) آبجکت globalThis در جاوا اسکریپت Es11
جاوا اسکریپت تو محیط ها و پلتفرم های مختلف اجرا میشه از جمله مرورگر، سرور (محیط نود جی اس)، تلفن های هوشمند و حتی سخت افزارهای رباتیک. object model تو هر کدوم از این محیط ها متفاوته، پس global object هم تو هر کدوم متفاوته، به همین خاطر اگه ما میخواستیم که یک کد یکسان رو برای پلتفرم های مختلف بنویسیم (cross-platform)، باید دقت میکردیم که تو هر پلتفرم، از global object خودش استفاده کنیم، مثلا :
-
- در مرورگر : از window یا frames
- در وب ورکر : از self
- در نود جی اس : از global
تو Es11 کلمه کلیدی globalThis به جاوا اسکریپت اضافه شده که تو هر پلفترم به global object خودش اشاره میکنه (فرقی نمیکنه که کد تو چه محیط و پلتفرمی اجرا بشه)، پس از این به بعد میتونیم با خیال راحت بدون در نظر گرفتن نوع پلتفرم و محیط اجرای جاوا اسکریپت، از globalThis به صورت مشترک استفاده کنیم. این کار باعث صرفه جویی در زمان کدنویسی میشه.
5) قابلیت ایمپورت پویا (Dynamic Import) در جاوا اسکریپت Es11
تو Es6 قابلیت برنامه نویسی ماژولار به جاوا اسکریپت اضافه شد. به این صورت که میتونستیم ماژول های مختلف رو جداگونه بنویسیم و export کنیم، و در نهایت ماژول ها رو تو صفحه ی که بهشون نیاز داشتیم (ابتدای صفحه) import کنیم. این مدل از ایمپورت کردن، ایمپورت ایستا یا istatic import بود. مثلا تو کد زیر، متغیر myExport رو از ماژول my-module.js که تو پوشه ی modules قرار داره به روش ایستا ایمپورت کردیم :
import { myExport } from "/modules/my-module.js";
اما فرض کنید میخوایم یه شرط بنویسیم که مثلا اگه شرط 1 برقرار بود ماژول 1 لود بشه و اگه شرط 2 برقرار بود ماژول 2 لود بشه، یا فرض کنید جایی نیاز داشته باشیم که ماژول مورد نظرمونو وسط کد لود کنیم (نه اول صفحه)، خب تو این شرایط قاعدتا قابلیت ایمپورت ایستا به کارمون نمیاد و ارور دریافت میکنیم. اینجا بود که تو Es11 قابلیت ایمپورت پویا یا dynamic import معرفی شد.
ایمپورت پویا یا dynamic import به ما این قابلیت رو میده که هرجای کد که نیاز داشتیم، ماژول مورد نظرمونو فراخوانی کنیم و این کار رو به کمک import() انجام میدیم که ورودیش آدرس ماژول مورد نظرمونه (در واقع میشه همون path ماژول که تو ایمپورت ایستا بعد از from مینوشتیم). همچنین از اونجایی که این نوع ایمپورت، برامون یه Promise رو برمیگردونه باید از دستور await ابتدای ایمپورت استفاده کنیم. داخل این پرامیس یه آبجکته که شامل همه ی export های ماژول مورد نظرمونه.
به عنوان مثال تو کد زیر یه تابع async نوشتیم که یه شرط فرضی رو چک میکنه و با توجه اینکه شرط برقرار هست یا نیست دو ماژول متفاوت رو ایمپورت میکنه و تو کنسول نمایش میده :
async function moduleLoading() {
if (conditon) {
await import("/modules/module1.js")
.then((module) => console.log(module))
.catch((error) => console.log(error));
} else {
await import("/modules/module2.js")
.then((module) => console.log(module))
.catch((error) => console.log(error));
}
}
6) عملگر نالیش ?? یا nullish coalescing operator در جاوا اسکریپت Es11
مقادیر falsy جاوا اسکریپت رو که میشناسید؟ همونطور که میدونید مقادیر 0, NaN, false, null, undefined و استرینگ خالی، همه مقادیر falsy یا ناصحیح جاوا اسکریپت هستن. یکی از کاربردهای عملگر || این بود که اگه مقدار سمت چپش falsy بود، مقدار سمت راست رو برمیگردوند (در غیر اینصورت مقدار سمت چپ رو برمیگردوند)، مثلا تو کد زیر چون همه مقادیر سمت چپ ناصحیح هستن، مقدار سمت راست رو برامون برمیگردونه :
console.log(0 || "Right"); // output: Right
console.log("" || "Right"); // output: Right
console.log(false || "Right"); // output: Right
console.log(undefined || "Right"); // output: Right
console.log(null || "Right"); // output: Right
تفاوت عملگر نالیش ?? با عملگر || اینه که به جای مقادیر falsy نسبت به مقادیر nullish حساسه، یعنی فقط null و undifiend ، و اگه یکی از این 2 تا رو سمت چپ خودش ببینه، مقدار سمت راست رو برمیگردونه (در غیر اینصورت، مقدار سمت چپ رو برمیگردونه). اگه مثال بالا رو با عملگر نالیش بازنویسی کنیم، 3 مورد اول نتیجه متفاوتی رو تو کنسول بهمون نشون میدن :
console.log(0 ?? "Right"); // output: 0
console.log("" ?? "Right"); // output: ""
console.log(false ?? "Right"); // output: false
console.log(undefined ?? "Right"); // output: Right
console.log(null ?? "Right"); // output: Right
نکته : اگه چند بار پشت سر هم از این عملگر استفاده کنیم، تا جایی که مقادیر null و undefined داشته باشیم، عملگر از روشون عبور میکنه و به اولین مقدار غیر از این 2 تا که میرسه برمیگردونه :
console.log(null ?? undefined ?? false); // output: false
نکته : اگه از دو عملگر || و ?? همزمان بخوایم استفاده کنیم، باید حتما یه طرف رو توی پرانتز بنویسیم (وگرنه ارور دریافت میکنیم) :
console.log(NaN ?? ("Yes" || "No")); // NaN
console.log((null ?? "Yes") || 0); // Yes
7) عملگر زنجیره اختیاری .? یا optional chaining operator در جاوا اسکریپت Es11
فرض کنید ما اطلاعات یوزرها رو در قالب آبجکت داریم. و این اطلاعات شامل نام و فامیل و آدرس هر یوزر هست. و فرضا خود آدرس هم شامل اسم خیابون و یه سری اطلاعات دیگه ست. حالا ما میخوایم اسم خیابون یوزرها رو فراخوانی کنیم. خب اگه یوزری اسم خیابون رو وارد نکرده باشه، با ارور مواجه میشیم :
const user = {
firtName: "Peyman",
lastName: "Bagheri",
};
console.log(user.address.street); // output: Uncaught TypeError: Cannot read properties of undefined (reading 'street')
کاری که قبلا میکردیم برای جلوگیری از این مشکل این بود که یه اول یه شرط رو چک میکردیم که مطمئن بشیم پراپرتی که میخوایم وجود داره و بعد فراخوانیش میکردیم. مثلا برای کد بالا به این شکل به جای ارور، undefined دریافت میکنیم :
console.log(
user.address
? user.address.street
? user.address.street.name
: undefined
: undefined
); // output: undefined
کار عملگر .? همینه! در واقع به ما این امکان رو میده که بدون نگرانی بابت وجود یا نبود یه پراپرتی، اون رو فراخوانی کنیم. در صورتی که وجود داشته باشه که بدون مشکل فراخوانی میشه و در صورتی که null یا undefined باشه، به جای ارور دادن، undefined برمیگردونه و کدمون بدون دریافت خطا کار میکنه. کد بالا رو با عملگر optional chaining باز نویسی میکنیم :
console.log(user?.address?.street?.name); // output: undefined
همونطور که دیدین تونستیم به کمک این عملگر چند خط کد رو تو یه خط خلاصه کنیم.
8) عملگر نسبت دهی منطقی اند =&& یا logical AND assignment operator در جاوا اسکریپت Es11
عملگر =&& بین دو value قرار میگیره و اگه اولی صحیح (true) باشه، مقدار دومی رو به اولی نسبت میده و assign میکنه. مثلا تو کد زیر چون x صحیحه، value دوم رو میپذیره اما y چون ناصحیحه، value دوم رو نمیپدیره :
let x = 10;
x &&= 5;
console.log(x); // output: 5
let y = 0;
y &&= 5;
console.log(y); // output: 0
نکته : اینجا چون قراره نسبت دهی (assignment) انجام بشه، نمیتونیم از const استفاده کنیم و باید از let استفاده کنیم.
9) عملگر نسبت دهی منطقی اور =|| یا logical AND assignment operator در جاوا اسکریپت Es11
عملگر =|| برعکس عملگر =&& عمل میکنه. یعنی اگه اولی ناصحیح (false) باشه، مقدار دومی رو به اولی نسبت میده و assign میکنه. اگه کد بالا رو با این عملگر بازنویسی کنیم، نتیجه معکوس رو میبینیم :
let x = 10;
x ||= 5;
console.log(x); // output: 10
let y = 0;
y ||= 5;
console.log(y); // output: 5
نکته : اینجا هم چون قراره نسبت دهی (assignment) انجام بشه، نمیتونیم از const استفاده کنیم و باید از let استفاده کنیم.
10) عملگر نسبت دهی نالیش =?? یا nullish coalescing assignment operator در جاوا اسکریپت Es11
عملگر =?? هم عملکردی مشابه دو عملگر قبلی داره با این تفاوت که نسبت دهی (assignment) رو به شرطی انجام میده که مقدار اولی null یا undefined باشه. مثلا تو کد زیر، فقط c , d نسبت دهی رو میپذیرن :
let a = 10;
console.log((a ??= 5)); // output: 10
let b = 0;
console.log((b ??= 5)); // output: 0
let c = null;
console.log((c ??= 5)); // output: 5
let d = undefined;
console.log((d ??= 5)); // output: 5
نکته : اینجا هم چون قراره نسبت دهی (assignment) انجام بشه، نمیتونیم از const استفاده کنیم و باید از let استفاده کنیم.
جمع بندی
شاید بشه گفت این ورژن، ورژن عملگرها بود ! 5 عملگر جدید تو این نسخه به جاوا اسکریپت اضافه شدن که خیلی جاها به کدنویسی سریعتر و بهینه تر کمک میکنن. قابلیت های دیگه یعنی ایمپورت پویا، دیتاتایپ جدید BigInt ، آبجکت globalThis و متدهای matchAll و allSettled هم که تو این ورژن به جاوا اسکریپت اضافه شدن فیچرهای بسیار خوبی هستن. اگه ایرادی تو آموزش دیدین یا سوالی داشتین خوشحال میشیم تو قسمت نظرات مطرح کنید. پیروز باشید.