معرفی ویژگی های ES10 – راهنمای کاربردی

۵۶ بازدید
آخرین به‌روزرسانی: ۱۴ شهریور ۱۴۰۲
زمان مطالعه: ۱۱ دقیقه
معرفی ویژگی های ES10 – راهنمای کاربردی

ES10 در حال حاضر صرفاً یک نسخه پیش‌نویس محسوب می‌شود. اما اغلب ویژگی‌ها به جز Object.fromEntries هم اینک در کروم پیاده‌سازی شده‌اند، بنابراین چرا نباید آن را از قبل مورد بررسی قرار دهیم؟ بدین ترتیب زمانی که مرورگرها شروع به پشتیبانی از آن بکنند، شما از قبل اطلاعات لازم را خواهید داشت. این راهنما برای افرادی که به بررسی ویژگی های ES10 نیاز دارند ضروری خواهد بود.

BigInt  - اعداد صحیح با دقت دلخواه

BigInt هفتمین نوع primitive است. BigInt یک عدد صحیح با دقت دلخواه است.

این بدان معنی ‌است که متغیرها می‌توانند اینک $$2^{53}$$ عدد را نمایش دهند و محدود به عدد 9007199254740992 نیستند.

const b = 1n; // append n to create a BigInt

در گذشته مقادیر صحیح بالاتر از 9007199254740992 پشتیبانی نمی‌شدند و در صورت تجاوز از این عدد مقدار مورد نظر به صورت MAX_SAFE_INTEGER + 1 قفل می‌شد.

const limit = Number.MAX_SAFE_INTEGER;
⇨ 9007199254740991

limit + 1;
⇨ 9007199254740992

limit + 2;
⇨ 9007199254740992 <--- MAX_SAFE_INTEGER + 1 exceeded

const larger = 9007199254740991n;
⇨ 9007199254740991n

const integer = BigInt(9007199254740991); // initialize with number
⇨ 9007199254740991n

const same = BigInt("9007199254740991"); // initialize with "string"
⇨ 9007199254740991n

Typeof

typeof 10;
⇨ 'number'

typeof 10n;
⇨ 'bigint'

عملگرهای تساوی می‌توانند بین این دو نوع استفاده شوند:

10n === BigInt(10);
⇨ true

10n == 10;
⇨ true

عملگرهای ریاضیاتی تنها درون نوع خاص خود عمل می‌کنند:

200n / 10n
⇨ 20n

200n / 20
⇨ Uncaught TypeError:
Cannot mix BigInt and other types، use explicit conversions <

علامت - ابتدایی کار می‌کند؛ اما علامت + ابتدایی کار نمی‌کند:

-100n
⇨ -100n

+100n
⇨ Uncaught TypeError:
Cannot convert a BigInt value to a number

در زمانی که این مقاله را می‌خوانید، احتمالاً matchAll به صورت رسمی در کروم نسخه C73 پیاده‌سازی شده است؛ اما اگر چنین نباشد نیز به خصوص اگر به «عبارت‌های منظم» (regex) علاقه‌مند باشید، بررسی آن ارزشش را دارد.

()string.prototype.matchAll

اگر در گوگل به دنبال عبارت javascript string match all بگردید، احتمالاً نخستین نتیجه، چیزی مانند زیر خواهد بود:

How do I write a regular expression to “match all”?

نتایج برتر استفاده از String.match را به همراه عبارت منظم g/ پیشنهاد می‌کنند. همچنین توصیه می‌شود که RegExp.exec و یا RegExp.test با g/ استفاده شود. ابتدا نگاهی به طرز کار مشخصات قدیمی می‌اندازیم.

String.match با آرگومان string تنها مطابقت first را بازگشت می‌دهد:

let string = “Hello”;
let matches = string.match(“l”);
console.log(matches[0]); // "l"

نتیجه کار یک «1» منفرد است. دقت کنید که مطابقت در [matches[0 و نه در matches ذخیره می‌شود. تنها «1» از جستجو برای «1» در کلمه “hello” بازگشت می‌یابد. همین نتیجه با استفاده از string.match به همراه یک آرگومان regex بازگشت می‌یابد. ما می‌توانیم کاراکتر «1» را در رشته «hello» با استفاده از عبارت منظم /l/ پیدا کنیم:

let string = "Hello";
let matches = string.match(/l/);
console.log(matches[0]); // "l"

افزودن g/ به ترکیب

اما String.match با یک عبارت منظم و فلگ g/ چندین مورد مطابقت بازگشت می‌دهد:

let string = "Hello";
let ret = string.match(/l/g); // (2) [“l”، “l”];

می‌بینید که ما مطابقت‌های چندگانه خود را با استفاده از نسخه‌های قبل از ES10 نیز به دست می‌آوریم و به خوبی کار می‌کند. بنابراین چرا باید خود را درگیر متد کاملاً جدید matchAll بکنیم؟ پیش از آن که بتوانیم به این سؤال با تفصیل پاسخ دهیم، باید به بررسی capture groups بپردازیم. این کار هیچ فایده‌ای هم که نداشته باشد، موجب می‌شود که با مفهوم جدیدی در مورد عبارت‌های منظم آشنا شوید.

گروه‌های Capture در عبارت منظم

گروه‌های capture در regex صرفاً اقدم به استخراج یک الگو از پرانتزها می‌کنند. شما می‌توانید گروه‌های مختلف را با (regex/.exec(string/ و با string.match، آن‌ها را capture کنید. گروه capture منظم از طریق پوشش یک الگو در (pattern) قابل اجرا است؛ اما برای ایجاد یک مشخصه groups روی شیء بازگشتی باید از (name>pattern>?) استفاده کنیم.

برای ایجاد یک نام گروه جدید، کافی است <name>? را به ابتدای عبارت داخل پرانتز اضافه کنید و در نتیجه مطابقت گروه‌بندی‌شده (pattern) به صورت groups.name درمی‌آید که به شیء match الصاق یافته است. در ادامه می‌توانید یک مثال عملی را مشاهده کنید.

نمونه رشته‌ای که باید مطابقت یابد:

در ادامه match.groups.color و match.groups.bird ایجاد می‌شوند:

1const string = 'black*raven lime*parrot white*seagull';
2const regex = /(?<color>.*?)\*(?<bird>[a-z0-9]+)/g;
3while (match = regex.exec(string))
4{
5    let value = match[0];
6    let index = match.index;
7    let input = match.input;
8    console.log(`${value} at ${index} with '${input}'`);
9    console.log(match.groups.color);
10    console.log(match.groups.bird);
11}

متد regex.exec باید چندین بار فراخوانی شود تا کل مجموعه نتایج جستجو پیمایش شود. در طی هر تکرار زمانی که exec. فراخوانی می‌شود، نتیجه بعدی مشخص می‌شود. از این رو از حلقه While استفاده می‌شود.

خروجی کنسول:

1black*raven at 0 with 'black*raven lime*parrot white*seagull'
2black
3raven
4lime*parrot at 11 with 'black*raven lime*parrot white*seagull'
5lime
6parrot
7white*seagull at 23 with 'black*raven lime*parrot white*seagull'
8white
9seagull

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

اینک ما اطلاعات مقدماتی کافی برای پاسخ دادن به سؤال مطرح شده در بخش پیشین را داریم.

دلایل کافی برای استفاده از ()matchAll.

  1. این گزینه می‌تواند در زمان استفاده از capture groups عملکرد بهتری داشته باشد. یک capture group در واقع صرفاً بخشی از عبارت منظم با پرانتز () است که یک الگو را استخراج می‌کند.
  2. این گزینه به جای آرایه یک iterator بازگشت می‌دهد. Iterator-ها خود اشیای مفیدی هستند.
  3. Iterator می‌تواند با استفاده از عملگر spread یعنی (...) به یک آرایه تبدیل شود.
  4. با استفاده از این گزینه دیگر نیازی به عبارت‌های منظم با g/ انتهایی وجود ندارد و بنابراین در مواردی که عبارت منظم از یک پایگاه داده یا منبع خارجی دریافت می‌شود و به همراه شیء RegEx مورد استفاده قرار می‌گیرد مناسب خواهد بود.
  5. عبارت‌های منظم که با استفاده از شیء RegEx ایجاد می‌شوند، نمی‌توانند با استفاده از عملگر نقطه (.) زنجیره‌سازی شوند.
  6. و نکته پیشرفته آخر این که شیء RegEx مشخصه درونی lastIndex. را که به ردگیری آخرین موقعیت مطابقت یافته می‌پردازد تغییر می‌دهد. این وضعیت می‌تواند موجب بروز خرابی در موارد پیچیده‌تر شود.

طرز کار ()matchAll. چگونه است؟

در ادامه طرز کار این متد را در حالت‌های مختلف بررسی می‌کنیم.

حالت ساده

در این بخش تلاش می‌کنیم همه موارد وجود حروف e و i را در کلمه hello بیابیم. از آنجا که یک iterator بازگشت می‌یابد، می‌توانیم این کار را با استفاده از یک حلقه for…of انجام دهیم:

1// Match all occurrences of the letters: "e" or "l" 
2let iterator = "hello".matchAll(/[el]/);
3for (const match of iterator)
4    console.log(match);

شما می‌توانید این بار از g/ استفاده نکنید، زیرا از سوی متد matchAll. لازم نیست. نتیجه کار چنین است:

1[ 'e', index: 1, input: 'hello' ] // Iteration 1
2[ 'l', index: 2, input: 'hello' ] // Iteration 2
3[ 'l', index: 3, input: 'hello' ] // Iteration 3

نمونه‌ای از گروه‌های capture با ()matchAll.

()matchAll. همه مزیت‌های فهرست شده فوق را دارد. این یک iterator است و از این رو می‌توانیم با استفاده از یک حلقه for…of آن را پیمایش کنیم. این همه تفاوت ساختاری آن را شامل می‌شود.

1const string = 'black*raven lime*parrot white*seagull';
2const regex = /(?<color>.*?)\*(?<bird>[a-z0-9]+)/;
3for (const match of string.matchAll(regex)) {
4    let value = match[0];
5    let index = match.index;
6    let input = match.input;
7    console.log(`${value} at ${index} with '${input}'`);
8    console.log(match.groups.color);
9    console.log(match.groups.bird);
10}

دقت کنید که g/ استفاده نشده است، زیرا از قبل از سوی ()matchAll. به کار گرفته شده است.

خروجی کنسول:

1black*raven at 0 with 'black*raven lime*parrot white*seagull'
2black
3raven
4lime*parrot at 11 with 'black*raven lime*parrot white*seagull'
5lime
6parrot
7white*seagull at 23 with 'black*raven lime*parrot white*seagull'
8white
9seagull

شاید از نظر زیبایی‌شناسی شباهت زیادی به پیاده‌سازی حلقه regex.exec اصلی داشته باشد؛ اما همان طور که پیش‌تر اشاره کردیم، روش بهتری محسوب می‌شود که دلایل آن را قبلاً بیان کردیم. همچنین حذف g/ موجب بروز یک حلقه نامتناهی نخواهد شد.

ایمپورت دینامیک

ایمپورت‌ها اینک می‌توانند به یک متغیر انتساب یابند:

1element.addEventListener('click', async () => {
2    const module = await import(`./api-scripts/button-click.js`);
3    module.clickEvent();
4});

()Array.flat

مسطح سازی یک آرایه چندبعدی به صورت زیر است:

1let multi = [1,2,3,[4,5,6,[7,8,9,[10,11,12]]]];
2multi.flat();               // [1,2,3,4,5,6,Array(4)]
3multi.flat().flat();        // [1,2,3,4,5,6,7,8,9,Array(3)]
4multi.flat().flat().flat(); // [1,2,3,4,5,6,7,8,9,10,11,12]
5multi.flat(Infinity);       // [1,2,3,4,5,6,7,8,9,10,11,12]

()Array.flatMap

این متد موجب می‌شود که:

1let array = [1, 2, 3, 4, 5];
2array.map(x => [x, x * 2]);

به صورت زیر درآید:

1[Array(2), Array(2), Array(2)]
20: (2)[1, 2]
31: (2)[2, 4]
42: (2)[3, 6]
53: (2)[4, 8]
64: (2)[5, 10]

و اگر این map مجدداً مسطح شود، به صورت زیر درمی‌آید:

1array.flatMap(v => [v, v * 2]);
2[1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

()Object.fromEntries

این متد یک لیست از جفت‌های کلید-مقدار را به یک شیء تبدیل می‌کند:

1let obj = { apple : 10, orange : 20, banana : 30 };
2let entries = Object.entries(obj);
3entries;
4(3) [Array(2), Array(2), Array(2)]
5 0: (2) ["apple", 10]
6 1: (2) ["orange", 20]
7 2: (2) ["banana", 30]
8let fromEntries = Object.fromEntries(entries);
9{ apple: 10, orange: 20, banana: 30 }

()String.trimStart و ()String.trimEnd

1let greeting = "     Space around     ";
2greeting.trimEnd();   // "     Space around";
3greeting.trimStart(); // "Space around     ";

()JSON.stringify با ترکیب متناسب

این به‌روزرسانی موجب اصلاح پردازش کاراکترهای U+D800 تا U+DFFF می‌شود که در برخی موارد ممکن است در رشته‌های JSON حضورداشته باشند. این مسئله نمی‌تواند موجب بروز مشکلی شود، زیرا JSON.stringify می‌تواند این اعداد را به صورت اعدادی بازگشت دهد که به صورت مقادیری قالب‌بندی شده‌اند که معادل کاراکترهای UTF-8 نیستند. اما قالب JSON الزام می‌کند که از انکودینگ UTF-8 استفاده کنیم.

شیء JSON می‌تواند برای تجزیه قالب JSON استفاده شود. شیء جاوا اسکریپت JSON دارای متدهای stringify و parse است.

متد parse یا رشته JSON خوش‌تعریف به صورت زیر کار می‌کند:

1'{ “prop1”: 1، "prop2": 2 }'; // A well-formed JSON format string

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

1'{ “prop1”: 1، "meth": () => {}}'; // Not JSON format string

قالب رشته JSON از Object Literal متفاوت است؛ گرچه تقریباً شبیه هم به نظر می‌رسند؛ اما نمی‌توان از هیچ نوع گیومه پیرامون نام مشخصه‌ها استفاده کرد و همچنین می‌تواند شامل متدهایی باشد که چنین چیزی در قالب رشته JSON مجاز نیست:

1let object_literal = { property: 1، meth: () => {} };

در هر حال، همه چیز درست به نظر می‌رسد. مثال نخست سازگار به نظر می‌رسد؛ اما مثال‌های ساده دیگری نیز وجود دارند که در اغلب موارد بدون مشکل عمل می‌کنند.

کاراکترهای U+2028 و U+2029

مشکل اینجاست. EcmaScript تا پیش از نسخه ES10 در عمل به طور کامل از قالب JSON پشتیبانی نمی‌کرد. کاراکتر جداساز خط یعنی U+2028 در موردی که escape نشده بود و کاراکتر جداساز پاراگراف یعنی U+2029 تا قبل از نسخه ES10 پذیرفته نبودند:

U+2028 کاراکتر جداساز خط است

U+2029 کاراکتر جداساز پاراگراف است. در برخی موارد می‌تواند در رشته قالب‌بندی شده JSON نیز ظاهر شود. همین نکته در مورد همه کاراکترهای بین U+D800 — U+DFFF نیز صدق می‌کند. اگر این کاراکترها در رشته قالب‌بندی شده JSON ظاهر می‌شدند، ممکن بود ساعت‌ها وقت شما صرف تلاش برای درک علت ایجاد خطا در جای دیگری از برنامه شود.

بنابراین اگر یک رشته مانند "('console.log(‘hello” را به eval ارسال کنید، با تبدیل کردن آن به کد واقعی، آن را به عنوان یک گزاره جاوا اسکریپت اجرا می‌کند. این وضعیت مشابه طرز کار JSON.parse برای پردازش رشته JSON است.

()Stable Array.prototype.sort

در پیاده‌سازی قبلی V8 از یک الگوریتم مرتب‌سازی سریع ناپایدار برای آرایه‌های شامل بیش از 10 آیتم استفاده شده است. یک الگوریتم مرتب‌سازی پایدار الگوریتمی است که وقتی دو شیء با کلیدهای مساوی و ترتیب یکسان ظاهر می‌شوند، خروجی را طوری مرتب‌سازی کند که این دو شیء در وضعیت ورودی قرار داشته باشند.

اما این وضعیت اینک در ES10 اصلاح شده است و یک مرتب‌سازی پایدار برای آرایه ارائه شده است:

1var fruit = [
2    { name: "Apple",      count: 13, },
3    { name: "Pear",       count: 12, },
4    { name: "Banana",     count: 12, },
5    { name: "Strawberry", count: 11, },
6    { name: "Cherry",     count: 11, },
7    { name: "Blackberry", count: 10, },
8    { name: "Pineapple",  count: 10, }
9];
10// Create our own sort criteria function:
11let my_sort = (a, b) => a.count - b.count;
12// Perform stable ES10 sort:
13let sorted = fruit.sort(my_sort);
14console.log(sorted);

خروجی کنسول (دقت کنید که آیتم‌ها با ترتیب معکوس نمایش می‌یابند):

متد جدید ()Function.toString

تابع‌ها شیء هستند و هر شیء یک متد ()toString. دارد، زیرا اساساً روی ()Object.prototype.toString قرار دارد. همه اشیا که شامل تابع‌ها نیز می‌شود از طریق وراثت از کلاس مبتنی بر پروتوتایپ از آن به ارث رسیده‌اند.

این بدان معنی است که ما هم اینک نیز متد ()funcion.toString را داریم؛ اما ES10 تلاش کرده است تا بازنمایی رشته را برای همه اشیا و تابع‌های داخلی استانداردسازی کند. در ادامه نمونه‌های مختلفی از کاربردهای گوناگون برای روشن‌تر شدن موضوع ارائه شده است:

مثال کلاسیک:

1function () { console.log('Hello there.'); }.toString();

خروجی کنسول (متن تابع در قالب رشته):

⇨ function () { console.log('Hello there.'); }

و در ادامه بقیه کاربردها را می‌بینید.

زمانی که مستقیماً از نام تابع ناشی می‌شود:

1Number.parseInt.toString();
 function parseInt() { [native code] }

با چارچوب محدود:

1function () { }.bind(0).toString();
⇨ function () { [native code] }

شیء تابع با قابلیت فراخوانی درونی:

1Symbol.toString();
⇨ function Symbol() { [native code] }

تابع ایجاد شده به صورت دینامیک:

1Function().toString();
⇨ function anonymous() {}

تابع generator ایجاد شده به صورت دینامیک:

1function* () { }.toString();
⇨ function* () { }

prototype.toString:

1Function.prototype.toString.call({});
⇨ Function.prototype.toString requires that 'this' be a Function"

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

اتصال Catch اختیاری

در گذشته، بند Catch در یک گزاره try / catch به صورت یک متغیر «الزامی» (required) بود. گزاره try / catch به ما کمک می‌کند که خطاها را در سطح ترمینال تفسیر کنیم. جهت یادآوری به مثال زیر توجه کنید:

1try {
2    // Call a non-existing function undefined_Function
3    undefined_Function("I'm trying");
4}
5catch(error) {
6    // Display the error if statements inside try above fail
7    console.log( error ); // undefined_Function is undefined
8}

اما در برخی موارد متغیر error الزامی بدون استفاده باقی می‌ماند:

1try {
2    JSON.parse(text); // <--- this will fail with "text not defined"
3    return true; <--- exit without error even if there is one
4}
5catch (redundant_sometmes) <--- this makes error variable redundant
6{
7    return false;
8}

هر کس که کد فوق را نوشته است، قصد داشته است از بند try با الزام به true بودن خارج شود. اما این اتفاق نمی‌افتد:

1(() => {
2    try {
3        JSON.parse(text)
4        return true
5    } catch(err) {
6        return false
7    }
8})()
9=> false

در ES10 اتصال خطای Catch اختیاری است. شما اینک می‌توانید از متغیر خطا رد شوید:

1try {
2    JSON.parse(text);
3    return true;
4}
5catch
6{
7    return false;
8}

در حال حاضر هیچ روشی مانند مثال قبل برای ارزیابی گزاره try وجود ندارد؛ اما زمانی که این گزاره مطرح شود، این بخش از مقاله را به‌روزرسانی خواهیم کرد.

شیء globalThis استاندارد شده

This سراسری تا قبل از ES10 استاندارد نشده بود. شما در کد اجرایی خود آن را خودتان روی چند پلتفرم با نوشتن کد زیر استانداردسازی می‌کردید:

1var getGlobal = function () {
2    if (typeof self !== 'undefined') { return self; }
3    if (typeof window !== 'undefined') { return window; }
4    if (typeof global !== 'undefined') { return global; }
5    throw new Error('unable to locate global object');
6};

اما همین کد نیز در همه موارد کار نمی‌کرد. بنابراین ES10 شیء globalThis را معرفی کرده است که باید از حالا به بعد برای دسترسی به دامنه عمومی روی هر پلتفرمی مورد استفاده قرار گیرد:

1// Access global array constructor
2globalThis.Array(0, 1, 2);
3[0, 1, 2]
4
5// Similar to window.v = { flag: true } in <= ES5
6globalThis.v = { flag: true };
7
8console.log(globalThis.v);
9{ flag: true }

Symbol.description

Description یک مشخصه فقط-خواندنی است که یک توصیف اختیاری از شیءهای symbol بازگشت می‌دهد.

1let mySymbol = 'My Symbol';
2let symObj = Symbol(mySymbol);
3symObj; // Symbol(My Symbol)
4String(symObj) === 'Symbol(${mySymbol})`); // true
5symObj.description; // "My Symbol"

دستور زبان Hashbang

Hashbang یا shebang که کاربران «یونیکس» (Unix) با آن آشنا هستند، به تعیین یک مفسر می‌پردازد که اقدام به اجرای کد جاوا اسکریپت می‌کند.

ES10 به استانداردسازی این shebang پرداخته است. ما قصد نداریم وارد جزییات آن شویم، زیرا از نظر فنی در واقع یک ویژگی زبان محسوب نمی‌شود؛ اما اساساً به متحد ساختن شیوه اجرای جاوا اسکریپت در سمت سرور می‌پردازد. در واقع ما به جای کد زیر:

$./index.js

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

$ node index.js

کلاس‌های ES10: اعضای خصوصی، استاتیک و عمومی

کاراکتر جدید ساختاری octothorpe (#) یا هشتگ اینک برای تعریف متغیرها، تابع‌ها، getter-ها و setter-ها به صورت مستقیم درون دامنه بدنه کلاس همراه با سازنده و متدهای کلاس می‌پردازد.

در ادامه یک نمونه هر چند بی‌معنی را مشاهده می‌کنید که تنها روی ساختار جدید متمرکز شده است:

1class Raven extends Bird {
2    #state = { eggs: 10};
3    // getter
4    get #eggs() { 
5        return state.eggs;
6    }
7    // setter
8    set #eggs(value) {
9        this.#state.eggs = value;
10    }
11    #lay() {
12        this.#eggs++;
13    }
14    constructor() {
15        super();
16        this.#lay.bind(this);
17    }
18    #render() {
19        /* paint UI */
20    }
21}

اگر بخواهیم صادق باشیم، این وضعیت موجب می‌شود که خوانایی این زبان کمی دشوارتر شود؛ اما با این حال همچنان یک ویژگی جالب محسوب می‌شود زیرا شبیه کلاس‌های ++C است.

دقت کنید که این ویژگی هنوز به طور کامل پیاده‌سازی نشده است.

نتیجه‌گیری

ES10 مجموعه ویژگی‌های جدیدی دارد که تا به حال فرصت بررسی کامل در «محیط اجرایی» (production) را نیافته‌اند. در این نوشته تلاش کردیم برخی از مهم‌ترین ویژگی‌های ES10 را معرفی کرده و مورد بررسی قرار دهیم. دقت داشته باشید که برخی از این ویژگی‌های هنوز در مرحله تکمیل شدن هستند و ممکن است تا زمان انتشار رسمی دچار تغییراتی شوند.

اگر این نوشته برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
js_tut
نظر شما چیست؟

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