اکمااسکریپت (ECMAScript) 2015 یا ES6، ششمین و بزرگترین نسخه استاندارد ECMAScript است که استاندارد پیادهسازی جاوااسکریپت را مشخص میکند. ES6 تغییرات زیادی را در زبان جاواسکریپت اعمال کرد.
بعد از این نسخه مقرر شده است که هر سال یک نسخه جدید با تغییرات کمتر توسط ECMAScript ارائه شود تا مانند ES6، شاهد تغییرات بسیار زیاد در یک نسخه نباشیم. نسخههای جدید ECMAScript را معمولا با نام ESNext یا +ES6 میشناسند.
مانند متغیرهای محدود به بلوک (block-scoped variables)، لوپ جدیدی برای آرایهها و اشیا و تغییرات بسیار زیاد دیگری که کار با جاوااسکریپت را راحت تر و دلچسب میکند. در ادامه به این تغییرات خواهیم پرداخت.
کلمه let برای تعریف متغیرها استفاده میشود. قبل از ES6 برای تعرریف یک متغیر باید از کلمهی var استفاده میکردیم.
دو تفاوت اساسی بین let و var وجود دارد. متغیرهایی که با var تعریف شدهاند function-scoped هستند و به بالای محدودهی خود به اصطلاح hoist میشوند ولی در عوض متغیرهایی که با let معرفی میشوند block-scoped هستند و hoist در آنها اتفاق نمیافتد.
// ES6 syntax for(let i = 0; i < 5; i++) { console.log(i); // 0,1,2,3,4 } console.log(i); // undefined // ES5 syntax for(var i = 0; i < 5; i++) { console.log(i); // 0,1,2,3,4 } console.log(i); // 5
همینطور که در بالا میبینید متغیر i در بلوک اول خارج از حلقهی for قابل دسترس نیست.
کلمهی جدید const این امکان تعریف مقادیر ثابت را فراهم میآورد. مقادیر ثابت (Constants)، فقط قابل خواندن هستند و امکان اختصاص (Assignment) دوباره را ندارند. همچنین مانند let، این مقادیر نیز block-scoped هستند.
const PI = 3.14; console.log(PI); // 3.14 PI = 10; // error
البته امکان تغییر ویژگیهای آرایهها و اشیا(Objects) وجود دارد.
// Changing object property value const PERSON = {name: "Peter", age: 28}; console.log(PERSON.age); // 28 PERSON.age = 30; console.log(PERSON.age); // 30 // Changing array element const COLORS = ["red", "green", "blue"]; console.log(COLORS[0]); // red COLORS[0] = "yellow" console.log(COLORS[0]); // yellow
حلقهی جدید for..of امکان تکرار (iterate) بر روی آرایهها و یا اشیا قابل تکرار (iterable) را به آسانی فراهم میکنند. همچنین کد داخل حلقه برای هر عنصر آرایه و یا شئ اجرا میشود.
// Iterating over array let letters = ["a", "b", "c", "d", "e", "f"]; for(let letter of letters) { console.log(letter); // a,b,c,d,e,f } // Iterating over string let greet = "Hello World!" for(let character of greet) { console.log(character); // H,e,l,l,o, ,W,o,r,l,d,! }
این حلقه بر روی اشیاء معمول جاوااسکریپت کار نمیکند. برای این اشیاء میتوان از حلقه for-in استفاده کرد.
تمپلیت لیترال امکان ساخت رشتههای (Strings) چند خطی و رشتههای قابل الحاق (interpolation) را فراهم میکنند. با استفاده از این امکان، میتوان متغیرها و عبارات (expressions) را در هر قسمتی از یک رشته وارد کرد.
تمپلیت لیترال با استفاده back-tick (``) نوشته میشوند و متغیر یا عبارتی که میخواهیم در آن وارد کنیم باید در داخل براکت بعد از یک علامت دلار ({...}$) نوشته شوند.
// Simple multi-line string let str = `The quick brown fox jumps over the lazy dog.`; // String with embedded variables and expression let a = 10; let b = 20; let result = `The sum of ${a} and ${b} is ${a+b}.`; console.log(result); // The sum of 10 and 20 is 30.
در ES5 برای نوشتن کد بالا باید به این شکل عمل میکردیم:
// Multi-line string var str = 'The quick brown fox\n\t' + 'jumps over the lazy dog.'; // Creating string using variables and expression var a = 10; var b = 20; var result = 'The sum of ' + a + ' and ' + b + ' is ' + (a+b) + '.'; console.log(result); // The sum of 10 and 20 is 30.
در ES6 میتوان برای توابع مقادیر پیشفرضی را برای پارامترها تعیین کرد بدین معنی که اگر در زمانی که یک تابع فراخوانده میشود آرگومانی به آن ارائه نشود، تابع از مقدار پیشفرض استفاده میکند:
function sayHello(name='World') { return `Hello ${name}!`; } console.log(sayHello()); // Hello World! console.log(sayHello('John')); // Hello John!
در ES5 برای نوشتن کد بالا باید به این شکل عمل میکردیم:
function sayHello(name) { var name = name || 'World'; return 'Hello ' + name + '!'; } console.log(sayHello()); // Hello World! console.log(sayHello('John')); // Hello John!
این نوع توابع امکان مختصر کردن نوشتن یک تابع expression را فراهم میکند. نوشتن این نوع توابع با استفاده از یک پیکان (<=) صورت میگیرد:
// Function Expression var sum = function(a, b) { return a + b; } console.log(sum(2, 3)); // 5 // Arrow function var sum = (a, b) => a + b; console.log(sum(2, 3)); // 5
همانطور که در بالا میبینید، کلمهی function و return در تعریف (declaration) تابع بالا وجود ندارد. زمانی که فقط یک پارامتر برای تابع وجود دارد، میتوان پرانتزها را نیز حذف کرد، ولی زمانی صفر پارامتر یا بیشتر از یک پارامتر وجود دارد، وجود پرانتزها الزامی است.
همچنین، زمانی که بیش از یک عبارت (expression) در تابع وجود دارد،باید از براکتها ({}) استفاده و کلمهی return در توابعی که مقدار بازگشتی دارند، وارد کرد.
// Single parameter, single statement var greet = name => alert("Hi " + name + "!"); greet("Peter"); // Hi Peter! // Multiple arguments, single statement var multiply = (x, y) => x * y; alert(multiply(2, 3)); // 6 // Single parameter, multiple statements var test = age => { if(age > 18) { alert("Adult"); } else { alert("Teenager"); } } test(21); // Adult // Multiple parameters, multiple statements var divide = (x, y) => { if(y != 0) { return x / y; } } alert(divide(10, 2)); // 5 // No parameter, single statement var hello = () => alert('Hello World!'); hello(); // Hello World!
یک تفاوت مهم بین این نوع توابع و توابع معمول وجود دارد. برعکس توابع معمول، این نوع توابع مقدار this که برای خودشان باشد را ندارند و این مقدار را از تابع خارجی، جایی که تعریف شدهاند، میگیرند. برای فهم بهتر مثال زیر را در نظر بگیرید:
function Person(nickname, country) { this.nickname = nickname; this.country = country; this.getInfo = function() { // Outer function context (Person object) return function() { // Inner function context (Global object 'Window') alert(this.constructor.name); // Window alert("Hi, I'm " + this.nickname + " from " + this.country); }; } } var p = new Person('Rick', 'Argentina'); var printInfo = p.getInfo(); printInfo(); // Hi, I'm undefined from undefined
نوشتن کد بالا با استفاده از توابع پیکانی و template literals:
function Person(nickname, country) { this.nickname = nickname; this.country = country; this.getInfo = function() { // Outer function context (Person object) return () => { // Inner function context (Person object) alert(this.constructor.name); // Person alert(`Hi, I'm ${this.nickname} from ${this.country}`); }; } } let p = new Person('Rick', 'Argentina'); let printInfo = p.getInfo(); printInfo(); // Hi, I'm Rick from Argentina
همانطور که در بالا مشخص است، کلمهی this در مثال بالا به زمینهی (context) تابعی که تابع پیکانی در آن قراردارد، که شیء Person میشود، اشاره دارد. ولی در مثال قبلی، کلمهی this به شیء گلوبال Window شاره دارد.
در نسخههای ES5 و قبلتر، در جاوااسکریپت هرگز کلاس وجود نداشت. کلاسها در نسخه ES6 معرفی شدهاند که بسیار شبیه به کلاسهای موجود در دیگر زبانهای برنامهنویسی شیءگرا مانند جاوا، پیاچپی و ...، هستند اما نحوه کار کلاسهای جاوااسکریپت کاملا شبیه به دیگر زبانهای برنامهنویسی نیست. کلاسهای ES6 ساخت اشیاء و پیاده سازی وراثت (inheritance) با استفاده از کلمهی extend را فراهم میکنند.
در ES6 میتوان یک کلاس را با استفاده از کلمهی class و نوشتن نام کلاس بعد از آن تعریف کرد. برای نوشتن نام یک کلاس، نوشتن آن به صورت TitleCase مرسوم است.
class Rectangle { // Class constructor constructor(length, width) { this.length = length; this.width = width; } // Class method getArea() { return this.length * this.width; } } // Square class inherits from the Rectangle class class Square extends Rectangle { // Child class constructor constructor(length) { // Call parent's constructor super(length, length); } // Child class method getPerimeter() { return 2 * (this.length + this.width); } } let rectangle = new Rectangle(5, 10); alert(rectangle.getArea()); // 50 let square = new Square(5); alert(square.getArea()); // 25 alert(square.getPerimeter()); // 20 alert(square instanceof Square); // true alert(square instanceof Rectangle); // true alert(rectangle instanceof Square); // false
در مثال بالا، کلاس Square از کلاس Rectangle، با استفاده از کلمهی extend، ارثبری میکند. ار کلاسهایی که از دیگر کلاسها ارثبری میکنند، به عنوان derived classes و یا child classes یاد میشود.
همچنین در کلاسهای فرزند، برا دسترسی به زمینه (context) در constructor، باید از کلمهی super استفاده کرد. به عنوان مثال اگر در مثال بالا super را حذف کنید و تابع getArea را بر روی شیء Square فراخوانی کنید، از آن جایی که تابع getArea نیاز به this دارد، ارور به وجود میآید.
نکته: برعکس توابع، تعریف کلاسها Hoist نمیشود. بنابراین، باید قبل از دسترسی به کلاس آن را تعریف کنیم وگرنه با ReferenceError میشویم.
قبل از ES6، پشتیبانی به صورت بومی (native) برای ماژولها در جاوااسکریپت وجود نداشت. همه چیز در یک برنامه جاوا اسکریپت، به طور مثال متغیرها در فایلهای مختلف، در یک محدوده (scope) بودند. ES6 ماژول برپایه فایل را معرفی کرد که در آن هر ماژول با یک فایل جدا ارائه میشود. حالا، میتوان از export و import در یک ماژول برای export یا import کردن متغیرها، توابع، کلاسها و غیره، از/به ماژولها و یا فایلهای دیگر استفاده کرد.
در مثال زیر یک فایل با نام main.js میسازیم:
let greet = "Hello World!" const PI = 3.14; function multiplyNumbers(a, b) { return a * b; } // Exporting variables and functions export { greet, PI, multiplyNumbers };
حال یک فایل دیگر با نام app.js درست میکنیم:
import { greet, PI, multiplyNumbers } from './main.js'; alert(greet); // Hello World! alert(PI); // 3.14 alert(multiplyNumbers(6, 15)); // 90
در آخر یک فایل HTML با نام test.html درست میکنیم (به "type="module در تگ script دقت کنید):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>ES6 Module Demo</title> </head> <body> <script type="module" src="app.js"> </body> </html>
نسخه ES6 ویژگی rest parameters معرفی کرده که به ما این اجازه را میدهد تا بتوان تعداد نامعلومی پارامتر را به یک تابع اختصاص داد. این ویژگی زمانی مفید است که میخواهید چند پارامتر به یک تابع بدهید ولی تعداد آن را نمیدانید.
یک rest parameter با اضافه کردن اپراتور rest (...) به یک پارامتر ساخته میشود. rest parameter را فقط میتوان به عنوان آخرین پارامتر یک تابع تعریف کرد و تنها یک rest parameter در هر تابع میتواند وجود داشته باشد.
function sortNames(...names) { return names.sort(); } alert(sortNames("Sarah", "Harry", "Peter")); // Harry,Peter,Sarah alert(sortNames("Tony", "Ben", "Rick", "Jos")); // John,Jos,Rick,Tony
زمانی که rest parameter تنها پارامتر موجود در تابع باشد، تمام آرگومانهای وارد شده به تابع را میگیرد، درغیر این صورت تمام پارامترهایی که بعد از پارامترهای نامگذاری شده را از آن خود میکند.
function myFunction(a, b, ...args) { return args; } alert(myFunction(1, 2, 3, 4, 5)); // 3,4,5 alert(myFunction(-7, 5, 0, -2, 4.5, 1, 3)); // 0,-2,4.5,1,3
نکته: rest parameter ارتباطی به REST (REpresentational State Transfer) ندارد.
این اپراتور، که با سه نقطه (...) مشخص میشود، دقیقا برعکس اپراتور rest عمل میکند. اپراتور spread (گسترش) یک آرایه را تفکیک و گسترده میکند و مقادیر آن را وارد تابع میکند.
function addNumbers(a, b, c) { return a + b + c; } let numbers = [5, 12, 8]; // ES5 way of passing array as an argument of a function alert(addNumbers.apply(null, numbers)); // 25 // ES6 spread operator alert(addNumbers(...numbers)); // 25
اپراتور spread همچنین میتواند عناصر یک تابع را، بدون استفاده از متدهای push()
, unshift()
concat()
و غیره، وارد یک آرایه دیگر کند.
let pets = ["Cat", "Dog", "Parrot"]; let bugs = ["Ant", "Bee"]; // Creating an array by inserting elements from other arrays let animals = [...pets, "Tiger", "Wolf", "Zebra", ...bugs]; alert(animals); // Cat,Dog,Parrot,Tiger,Wolf,Zebra,Ant,Bee
این یک عبارت (expression) که خارج کردن مقادیر از یک آرایه یا ویژگیها از یک شیء و تبدیل آنها به متغیرهای مجزا را آسان میکند.
دو نوع destructing assignment وجود دارد. یک نوع برای آرایهها و یکی برای اشیاء.
قبل از ES6 برای گرفتن یک مقدار مجزا از یک آرایه باید به صورت زیر عمل میکردیم:
// ES5 syntax var fruits = ["Apple", "Banana"]; var a = fruits[0]; var b = fruits[1]; alert(a); // Apple alert(b); // Banana
در ES6 به این گونه عمل میکنیم:
// ES6 syntax let fruits = ["Apple", "Banana"]; let [a, b] = fruits; // Array destructuring assignment alert(a); // Apple alert(b); // Banana
همچنین میتوان از اپراتور rest نیز استفاده کرد:
// ES6 syntax let fruits = ["Apple", "Banana", "Mango"]; let [a, ...r] = fruits; alert(a); // Apple alert(r); // Banana,Mango alert(Array.isArray(r)); // true
قبل از ES6 برای گرفتن مقادیر ویژگی اشیاء باید به صورت زیر عمل میکردیم:
// ES5 syntax var person = {name: "Peter", age: 28}; var name = person.name; var age = person.age; alert(name); // Peter alert(age); // 28
در ES6 به این گونه عمل میکنیم:
// ES6 syntax let person = {name: "Peter", age: 28}; let {name, age} = person; // Object destructuring assignment alert(name); // Peter alert(age); // 28
بیشتر ویژگیهای توضیح داده شده در بالا توسط مروگرها پشتیبانی میشود. هرچند میتوان از ابزار ترجمه کد آنلاین Babel نیز برای تبدیل کد ES6 به ES5 به صورت رایگان استفاده کرد.
ترجمهای از مقاله JavaScript ES6 Features