یادم می‌آید اولین بار که کد ES6 دیدم، فکر کردم با یک زبان دیگر طرفم. یک فایل جاوااسکریپت که قبلاً چهل خط بود، حالا در هشت خط نوشته شده بود و کار مشابهی انجام می‌داد. آن روز برایم روشن شد که ES6 فقط یک آپدیت سینتکسی نیست؛ یک تغییر فلسفی در نحوه‌ی نوشتن جاوااسکریپت است — از زبانی که با کمبود امکانات دست‌وپنجه نرم می‌کرد، به زبانی که ابزارهای مدرن در اختیار توسعه‌دهنده می‌گذارد. سال‌ها بعد، وقتی در پروژه‌های واقعی با React، Node.js و TypeScript کار می‌کردم، فهمیدم که بدون تسلط بر ES6، هر کدی که می‌نویسم، یک قدم عقب‌تر از دنیای حرفه‌ای است. در این مقاله، همان ویژگی‌های ES6 را مرور می‌کنم که در پروژه‌های واقعی هر روز از آن‌ها استفاده می‌کنم — نه به‌عنوان فهرست خشک، بلکه با تمرکز بر این‌که هر ویژگی چه دردی را درمان می‌کند و کجا بیشترین اثر را دارد.

چرا ES6 نقطه‌ی عطف جاوااسکریپت است؟

اگر تازه با جاوااسکریپت آشنا می‌شوید، پیشنهاد می‌کنم اول آموزش جاوااسکریپت از صفر را بخوانید و سپس مفاهیم پایه جاوااسکریپت را مرور کنید. ES6 که با نام ECMAScript 2015 هم شناخته می‌شود، نسخه‌ای است که در سال ۲۰۱۵ منتشر شد و به‌طور رسمی زبان را وارد دوران مدرن کرد. قبل از آن، آخرین تغییر بزرگ جاوااسکریپت به نسخه‌ی ES5 برمی‌گشت که در سال ۲۰۰۹ منتشر شده بود — یعنی شش سال رکود در تکامل زبان.

سه دلیل که ES6 را به نقطه‌ی عطف تبدیل می‌کند:

  • حل مشکلات دیرینه: مشکلاتی مثل دامنه‌ی var، مدیریت this در callbackها، و ساخت رشته‌های پیچیده، سال‌ها توسعه‌دهندگان را آزار داده بود. ES6 هرکدام از این‌ها را با یک راه‌حل بومی حل کرد.
  • پایه‌ریزی اکوسیستم مدرن: React، Vue، Node.js مدرن، TypeScript و تقریباً همه‌ی کتابخانه‌های امروزی، بر پایه‌ی امکانات ES6 ساخته شده‌اند. اگر ES6 را ندانید، کد این ابزارها برایتان گیج‌کننده می‌شود.
  • تعریف نسخه‌بندی سالانه: از ES6 به بعد، تصمیم گرفته شد که هر سال یک نسخه‌ی کوچک‌تر منتشر شود (ES2016، ES2017 و...). این تغییر در فرآیند، سرعت تکامل زبان را چند برابر کرد.
ES6 فقط یک آپدیت سینتکسی نبود؛ پایان یک دوران و آغاز دورانی بود که در آن، جاوااسکریپت به زبان جدی برای پروژه‌های بزرگ تبدیل شد.

let و const: پایان دوران var

شاید معروف‌ترین ویژگی ES6، معرفی let و const به‌جای var است. تفاوت اصلی در دامنه است:

// var دامنه‌ی تابعی دارد
function test() {
    if (true) {
        var x = 5;
    }
    console.log(x); // 5 — بیرون از بلوک if قابل دسترسی
}

// let/const دامنه‌ی بلوکی دارند
function test2() {
    if (true) {
        let y = 5;
    }
    console.log(y); // ReferenceError
}

سه نکته‌ی مهم در استفاده از let و const که در پروژه‌های واقعی به آن‌ها رسیده‌ام:

۱) const را پیش‌فرض بگیرید

در کدهای مدرن، توصیه می‌شود همیشه از const شروع کنید و اگر واقعاً نیاز به تغییر داشتید، به let تغییر دهید. این رویکرد، به‌طور ناخودآگاه شما را به کدی سوق می‌دهد که حالت‌های کمتری دارد و در دیباگ ساده‌تر است.

۲) const به‌معنی «غیرقابل تغییر» نیست

یک سوءتفاهم رایج: const فقط مانع تخصیص مجدد متغیر می‌شود، ولی محتوای یک آبجکت یا آرایه‌ی const قابل تغییر است:

const user = { name: "Ali" };
user.name = "Sara";  // مجاز است
// user = {};         // خطا

const numbers = [1, 2, 3];
numbers.push(4);     // مجاز است
// numbers = [];      // خطا

برای غیرقابل تغییر کردن واقعی، باید از Object.freeze استفاده کنید — ولی این هم فقط سطح اول را فریز می‌کند، نه تودرتو.

۳) for...let دامنه‌ی بلوکی در حلقه

یکی از تفاوت‌های کلاسیک var و let در حلقه‌ها ظاهر می‌شود:

// با var — همه یک مقدار چاپ می‌کنند
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// خروجی: 3, 3, 3

// با let — هر کدام مقدار خودش
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// خروجی: 0, 1, 2

این تفاوت، منبع یکی از معروف‌ترین باگ‌های جاوااسکریپت قبل از ES6 بود. تجربه‌ی من: بعد از این‌که let را جدی گرفتم، تعداد باگ‌های مربوط به closure در حلقه‌ها به صفر رسید. اصول کامل دامنه و hoisting را در مفاهیم پایه جاوااسکریپت آورده‌ام.

Arrow Function: توابع بدون دردسر

Arrow functionها، سینتکس کوتاه‌تری برای تعریف تابع ارائه می‌دهند و مهم‌تر از آن، رفتار this را ساده می‌کنند:

// تابع معمولی
const add = function (a, b) {
    return a + b;
};

// Arrow function
const add2 = (a, b) => a + b;

// با یک پارامتر
const double = x => x * 2;

// با بدنه‌ی چندخطی
const process = (items) => {
    const filtered = items.filter((i) => i.active);
    return filtered.map((i) => i.name);
};

تفاوت مهم: this در Arrow Function

مهم‌ترین تفاوت arrow function با تابع معمولی، رفتار this است. Arrow function مقدار this را از محیط بیرونی می‌گیرد (lexical)، در حالی که تابع معمولی this را بسته به نحوه‌ی فراخوانی تعیین می‌کند:

const timer = {
    seconds: 0,

    start() {
        // تابع معمولی — this به window اشاره می‌کند
        setInterval(function () {
            this.seconds++; // NaN
        }, 1000);

        // Arrow function — this به timer اشاره می‌کند
        setInterval(() => {
            this.seconds++; // درست کار می‌کند
        }, 1000);
    },
};

در پروژه‌های واقعی، این ویژگی باعث شده که ۹۰٪ کدهای callback را با arrow function بنویسم. تنها جاهایی که از تابع معمولی استفاده می‌کنم:

  • متدهای آبجکت که به this خودشان نیاز دارند.
  • توابع سازنده (constructor) — که امروز معمولاً با class نوشته می‌شوند.
  • توابعی که می‌خواهند arguments را ببینند.

اصول کامل arrow function و this در شی‌گرایی در جاوااسکریپت با مثال‌های بیشتر آمده است.

Arrow function، نه یک سینتکس کوتاه‌تر، بلکه یک قرارداد متفاوت درباره‌ی this است؛ اگر این تفاوت را ندانید، کدتان اجرا می‌شود ولی رفتارش شما را غافلگیر می‌کند.

Template Literal: رشته‌های خوانا

Template literalها با backtick (`) نوشته می‌شوند و دو مزیت اصلی دارند: درج متغیر و رشته‌های چندخطی.

const name = "Ali";
const age = 30;

// قدیم: چسباندن با +
const old = "My name is " + name + " and I am " + age + " years old.";

// مدرن: با template literal
const modern = `My name is ${name} and I am ${age} years old.`;

// چندخطی
const message = `
  Hello ${name},
  Welcome to our platform.
  Your account has been activated.
`;

// محاسبه در template
const total = `Total: ${(5 + 3) * 2}`;

در پروژه‌های واقعی، template literal جایگزین تقریباً همه‌ی کاربردهای چسباندن رشته با + شده است. سه دلیل:

  • خوانایی: مقایسه‌ی کد بالا نشان می‌دهد که چقدر واضح‌تر است.
  • چندخطی: بدون نیاز به \n یا کاراکترهای فرار عجیب.
  • درج محاسبه: می‌توانید هر عبارت جاوااسکریپتی را در ${} قرار دهید.

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

Destructuring: استخراج با دستمزد کم

Destructuring به شما امکان می‌دهد داده را از آرایه یا آبجکت، در یک خط استخراج کنید. این ویژگی، کد را بسیار کوتاه‌تر و خواناتر می‌کند:

Destructuring آبجکت

const user = {
    name: "Ali",
    age: 30,
    email: "ali@example.com",
};

// قدیم
const name1 = user.name;
const age1 = user.age;

// مدرن
const { name, age, email } = user;

// با نام جدید
const { name: userName, age: userAge } = user;

// با مقدار پیش‌فرض
const { city = "Unknown" } = user;

Destructuring آرایه

const point = [10, 20, 30];

const [x, y, z] = point;

// پرش از یک عنصر
const [first, , third] = point;

// با rest
const [head, ...tail] = point;

Destructuring در پارامتر تابع

یکی از پرکاربردترین شکل‌های destructuring در پروژه‌های واقعی، استفاده در امضای تابع است:

// قدیم
function createUser(options) {
    const name = options.name;
    const email = options.email;
    const role = options.role || "user";
    // ...
}

// مدرن
function createUser({ name, email, role = "user" }) {
    // مستقیماً از متغیرها استفاده کنید
}

// Destructuring تودرتو
function processResponse({ data: { items, total }, status }) {
    console.log(items, total, status);
}

تجربه‌ی من: بعد از تسلط بر destructuring، حجم کدهای استخراج داده در پروژه‌های من به‌طور محسوس کاهش یافت و خوانایی افزایش پیدا کرد. مخصوصاً در پروژه‌های React، که props و state مدام از هم جدا می‌شوند، این ویژگی یک نعمت است.

Spread و Rest: سه نقطه، دو کاربرد

سه‌نقطه (...) در ES6 دو کاربرد متفاوت دارد که هر دو بسیار پرکاربردند:

Spread: پخش کردن

// در آرایه
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];

// کپی سطحی
const copy = [...arr1];

// در آبجکت
const user = { name: "Ali", age: 30 };
const updated = { ...user, age: 31 };

// در فراخوانی تابع
const numbers = [3, 1, 2];
Math.max(...numbers); // 3

Rest: جمع کردن

// در پارامتر تابع
function sum(...numbers) {
    return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10

// در destructuring
const [first, ...rest] = [1, 2, 3, 4];
// first = 1, rest = [2, 3, 4]

const { name, ...others } = { name: "Ali", age: 30, email: "ali@e.com" };
// name = "Ali", others = { age: 30, email: "ali@e.com" }

سه نکته‌ی مهم در استفاده از spread که در پروژه‌های واقعی به آن‌ها رسیده‌ام:

  • کپی سطحی، نه عمیق: spread فقط لایه‌ی اول را کپی می‌کند. اگر آبجکت تودرتو دارید، همچنان با reference مشترک کار می‌کنید:
const original = { user: { name: "Ali" } };
const copy = { ...original };
copy.user.name = "Sara";
console.log(original.user.name); // Sara — چون تودرتو مشترک است
  • عملکرد spread در آرایه‌های بزرگ: در آرایه‌های بزرگ، spread یک کپی کامل می‌سازد. برای آرایه‌های میلیون عضوی، این کپی می‌تواند کند باشد. راه‌حل: به‌جای spread، از همان آرایه استفاده کنید یا از تکنیک‌های بهینه‌تر — اصول کامل در متدهای آرایه در جاوااسکریپت آمده است.
  • ترتیب spread در آبجکت مهم است: در { ...a, ...b }، اگر کلید تکراری باشد، مقدار b برنده است چون بعد از a آمده. این ویژگی برای override کردن مقادیر، بسیار مفید است.

پارامترهای پیش‌فرض

در ES6، می‌توانید مقدار پیش‌فرض برای پارامترهای تابع تعیین کنید:

// قدیم
function greet(name) {
    name = name || "Guest";
    return `Hello, ${name}!`;
}

// مدرن
function greet2(name = "Guest") {
    return `Hello, ${name}!`;
}

// پیش‌فرض بر اساس پارامتر دیگر
function createUser(name, role = name === "admin" ? "super" : "user") {
    return { name, role };
}

تفاوت مهم با روش قدیمی: در روش ||، مقادیر Falsy مثل 0 و "" هم به پیش‌فرض تبدیل می‌شوند. در روش جدید، فقط undefined باعث استفاده از پیش‌فرض می‌شود:

function count(value) {
    value = value || 10;
    return value;
}
count(0); // 10 — اشتباه!

function count2(value = 10) {
    return value;
}
count2(0); // 0 — درست

این تفاوت ظریف، در پروژه‌های واقعی منبع باگ‌های پنهان است — مخصوصاً در فرم‌ها که مقدار 0 یا رشته‌ی خالی، مقدار معناداری است.

کلاس‌ها در ES6

قبل از ES6، وراثت در جاوااسکریپت با prototype پیاده می‌شد و کد زشت و پیچیده‌ای داشت. ES6 سینتکس class را معرفی کرد که خوانایی را چند برابر افزایش داد:

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        return `${this.name} makes a sound`;
    }

    static create(name) {
        return new Animal(name);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }

    speak() {
        return `${this.name} barks`;
    }
}

const rex = new Dog("Rex", "Labrador");
console.log(rex.speak()); // Rex barks

سه نکته‌ی مهم در استفاده از class که در پروژه‌های واقعی به آن‌ها رسیده‌ام:

۱) class سینتکس است، نه مفهوم جدید

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

۲) متدهای استاتیک

متدهای static به نمونه‌ی خاصی وابسته نیستند و روی خود کلاس فراخوانی می‌شوند. این الگو، برای سازنده‌های جانشین (alternative constructor) بسیار مفید است — معادل @classmethod در پایتون، که در شی‌گرایی در پایتون توضیح داده‌ام.

۳) فیلدهای خصوصی (Private Fields)

در نسخه‌های جدیدتر از ES2022، امکان تعریف فیلدهای خصوصی با # اضافه شد:

class Account {
    #balance = 0;

    deposit(amount) {
        this.#balance += amount;
    }

    get balance() {
        return this.#balance;
    }
}

const acc = new Account();
acc.deposit(100);
console.log(acc.balance); // 100
console.log(acc.#balance); // SyntaxError

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

کلاس در ES6، جادو نیست؛ یک بسته‌بندی خوانا برای همان prototype است. اگر prototype را نفهمید، class فقط یک ماسک است که در روز دیباگ برداشته می‌شود.

ماژول‌ها: import و export

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

// math.js
export const PI = 3.14159;

export function add(a, b) {
    return a + b;
}

export default function multiply(a, b) {
    return a * b;
}

// main.js
import multiply, { PI, add } from "./math.js";

console.log(PI);           // 3.14159
console.log(add(2, 3));    // 5
console.log(multiply(2, 3)); // 6

سه نکته‌ی مهم در ماژول‌ها که در پروژه‌های واقعی به آن‌ها رسیده‌ام:

  • export default در برابر export نام‌دار: هر فایل، فقط می‌تواند یک export default داشته باشد، ولی چندین export نام‌دار می‌تواند داشته باشد. توصیه‌ی من: ترجیحاً از export نام‌دار استفاده کنید، چون در import، صریح‌تر است و ابزارهای IDE بهتر می‌توانند refactor کنند.
  • tree-shaking: یکی از مزایای مهم ماژول‌ها این است که ابزارهای build مثل Webpack و Vite می‌توانند کدهایی که استفاده نمی‌شوند را حذف کنند. برای این‌که این مزیت فعال باشد، از export نام‌دار به‌جای default استفاده کنید.
  • live binding: در ماژول‌های ES6، وقتی یک متغیر export می‌شود، import آن یک binding زنده است — یعنی اگر مقدار در ماژول اصلی تغییر کند، در ماژول import‌شده هم دیده می‌شود. این رفتار با ماژول‌های CommonJS (که در Node.js قدیمی استفاده می‌شد) متفاوت است.

نکته‌ی مهم عملی: ماژول‌های ES6 در مرورگرها نیاز به ویژگی type="module" دارند:

<script type="module" src="main.js"></script>

با این ویژگی، متغیرهای top-level در ماژول، دیگر به‌طور خودکار به window اضافه نمی‌شوند — که خودش یک مزیت امنیتی و نگهداری است.

Iterators، Generators و for...of

ES6 مکانیزم iterator را به‌طور رسمی وارد زبان کرد. این مفهوم، پایه‌ی for...of و بسیاری از الگوهای مدرن است:

// for...of روی هر شیء iterable کار می‌کند
const fruits = ["apple", "banana", "cherry"];

for (const fruit of fruits) {
    console.log(fruit);
}

// Generators: توابعی که می‌توانند متوقف و از سر گرفته شوند
function* countdown(start) {
    while (start > 0) {
        yield start;
        start--;
    }
}

for (const num of countdown(3)) {
    console.log(num); // 3, 2, 1
}

Generatorها در پروژه‌های واقعی، به‌ویژه در پردازش داده‌های حجیم، بسیار مفید هستند — چون می‌توانند داده را تکه‌تکه تولید کنند بدون این‌که همه‌چیز در حافظه بار شود. این مفهوم، معادل نزدیک yield در پایتون است که در آموزش پایتون از صفر توضیح داده‌ام. تجربه‌ی من: در پروژه‌ای که یک فایل چند گیگابایتی CSV را پردازش می‌کردیم، Generatorها توانستند مصرف حافظه را از چند گیگابایت به چند مگابایت کاهش دهند.

Map، Set و ساختارهای داده‌ی جدید

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

Map: دیکشنری با کلید هر نوعی

const map = new Map();

map.set("name", "Ali");
map.set(42, "answer");
map.set({ id: 1 }, "object key");

console.log(map.get("name")); // Ali
console.log(map.size);        // 3

for (const [key, value] of map) {
    console.log(key, value);
}

تفاوت اصلی Map با آبجکت معمولی: کلیدها می‌توانند هر نوعی باشند (شامل آبجکت‌ها)، ترتیب درج حفظ می‌شود، و متدهای قدرتمندی مثل size، has و delete دارد. در پروژه‌های واقعی، Map انتخاب بهتری است وقتی کلیدها از جنس غیر رشته هستند یا تعداد زیادی insert/delete دارید.

Set: مجموعه‌ی مقادیر یکتا

const set = new Set([1, 2, 3, 3, 2, 1]);
console.log(set.size); // 3

set.add(4);
console.log(set.has(4)); // true

// حذف تکراری‌ها از آرایه
const unique = [...new Set([1, 2, 2, 3, 3, 3])];
// [1, 2, 3]

الگوی [...new Set(arr)] برای حذف تکراری‌ها، یکی از پرکاربردترین الگوهای ES6 در پروژه‌های واقعی من است — جایگزین ساده‌ای برای حلقه‌های دستی که قبلاً می‌نوشتم. اصول کار با آرایه و مقایسه‌ی روش‌های مختلف حذف تکراری در متدهای آرایه در جاوااسکریپت آمده است.

Optional Chaining و Nullish Coalescing

دو ویژگی که در ES2020 اضافه شدند ولی امروز بخش جدانشدنی کد مدرن هستند:

Optional Chaining: دسترسی امن

// قدیم
const city = user && user.address && user.address.city;

// مدرن
const city = user?.address?.city;

// روی متد
const result = obj.method?.();

// روی آرایه
const first = arr?.[0];

Nullish Coalescing: پیش‌فرض دقیق‌تر

// تفاوت || و ??
const a = 0 || "default";    // "default"
const b = 0 ?? "default";    // 0

// کاربرد واقعی
function createUser({ name, role }) {
    return {
        name: name ?? "Guest",
        role: role ?? "user",
    };
}

تجربه‌ی من: ترکیب این دو ویژگی، حجم کد دفاعی (defensive code) را به‌شدت کاهش داده. قبلاً برای هر دسترسی تودرتو، یک زنجیره‌ی && می‌نوشتم که خوانایی را پایین می‌آورد و گاهی خطاهای منطقی می‌ساخت. با optional chaining، این کار یک خط است.

الگوهای واقعی در پروژه‌های مدرن

چند الگویی که با کمک ویژگی‌های ES6 در پروژه‌های واقعی به الگوهای ذهنی من تبدیل شده‌اند:

الگوی اول: به‌روزرسانی حالت بدون تغییر اصلی

function updateUser(user, updates) {
    return { ...user, ...updates };
}

// به‌روزرسانی حالت تودرتو
function updateAddress(user, newCity) {
    return {
        ...user,
        address: {
            ...user.address,
            city: newCity,
        },
    };
}

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

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

const result = users
    .filter((u) => u.active)
    .map((u) => ({ ...u, name: u.name.trim() }))
    .sort((a, b) => a.name.localeCompare(b.name))
    .slice(0, 10);

زنجیره‌ی filter → map → sort، جایگزین حلقه‌های طولانی شده و خوانایی را به‌شدت بالا می‌برد. این الگو، پایه‌ی پردازش داده در پروژه‌های مدرن است.

الگوی سوم: تنظیمات با پیش‌فرض

const DEFAULT_CONFIG = {
    timeout: 5000,
    retries: 3,
    headers: {},
};

function createClient(config = {}) {
    const finalConfig = {
        ...DEFAULT_CONFIG,
        ...config,
        headers: {
            ...DEFAULT_CONFIG.headers,
            ...config.headers,
        },
    };

    return finalConfig;
}

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

اشتباهاتی که در پروژه‌های واقعی دیده‌ام

در بازبینی کد پروژه‌های مختلف، این اشتباهات را زیاد دیده‌ام:

  • استفاده از var در کد جدید: هنوز در بعضی پروژه‌ها این عادت قدیمی مانده. استفاده از let و const هم امن‌تر است و هم خواناتر.
  • arrow function در متدهای آبجکت: اگر متد آبجکت را با arrow function تعریف کنید، this به آبجکت اشاره نمی‌کند بلکه به دامنه‌ی بیرونی. این باگ در پروژه‌های واقعی زیاد دیده می‌شود.
  • انتظار کپی عمیق از spread: spread فقط کپی سطحی می‌سازد. برای کپی عمیق، باید از structuredClone یا کتابخانه‌های تخصصی استفاده کنید.
  • نادیده‌گرفتن تفاوت || و ??: برای مقادیر عددی که صفر معنادار است، استفاده از || باگ می‌سازد. از ?? استفاده کنید.
  • comprehension بیش‌ازحد در destructuring: destructuring تودرتوی بیش از دو سطح، خوانایی را از بین می‌برد. در آن حالت، متغیرهای میانی بسازید.
  • نادیده‌گرفتن تفاوت Map و آبجکت: بعضی تیم‌ها برای همه‌چیز از آبجکت استفاده می‌کنند. Map در سناریوهای insert/delete زیاد، عملکرد بهتری دارد.
  • عدم استفاده از ماژول‌ها: کد همه‌چیز در یک فایل بزرگ. این عادت، در پروژه‌های واقعی به کابوس نگهداری منجر می‌شود. اصول ماژول‌بندی در آموزش ES6 آمده است.
  • ترکیب module و non-module: در یک پروژه، بهتر است همه‌چیز ماژول باشد. ترکیب کد ماژولار با اسکریپت‌های سنتی، منبع باگ‌های عجیب است.
  • نادیده‌گرفتن محدودیت مرورگر: بعضی ویژگی‌های ES6+ در مرورگرهای قدیمی پشتیبانی نمی‌شوند. برای پروژه‌های با مخاطب متنوع، باید از transpiler (مثل Babel) یا polyfill استفاده کنید.
  • قربانی‌کردن خوانایی برای سینتکس کوتاه: بعضی توسعه‌دهندگان همه‌چیز را در یک خط و با زنجیره‌ی روش‌ها می‌نویسند. در برخی موارد، شکستن به دو یا سه خط خواناتر است.
  • تکیه بر class به‌جای composition: class ابزار خوبی است، ولی در JavaScript مدرن، الگوی composition با توابع و آبجکت‌ها معمولاً انعطاف‌پذیرتر و خواناتر است — همان اصلی که در شی‌گرایی در جاوااسکریپت باز کرده‌ام.
  • نبود مستندسازی در کدهای پیچیده: زنجیره‌های طولانی از متدها و destructuringهای عمیق، برای خواننده‌ی بعدی سخت فهم است. یک کامنت کوتاه، ارزش زیادی در نگهداری پروژه دارد.

یک توصیه‌ی عملی از تجربه: در شروع هر پروژه، از یک linter مثل ESLint استفاده کنید که قواعد مدرن را اعمال کند. Linter جلوی خیلی از این اشتباهات را می‌گیرد و به‌تدریج، این عادت‌ها به ذهن شما منتقل می‌شوند. اصول کامل این ابزار را در مقالات مربوط به افزونه‌های VS Code و ESLint در برابر Prettier توضیح داده‌ام.

مسیر ادامه‌ی یادگیری

ES6، نقطه‌ی شروعی برای جاوااسکریپت مدرن است — نه پایان راه. سه نکته‌ی اصلی که در این مقاله به آن‌ها رسیدیم: اول، ویژگی‌های ES6 فقط سینتکس کوتاه‌تر نیستند؛ هرکدام یک تفاوت رفتاری دارند که در پروژه‌های واقعی اثر می‌گذارند (مثل رفتار this در arrow function یا کپی سطحی در spread)؛ دوم، تسلط بر این ویژگی‌ها، پیش‌نیاز کار با اکوسیستم مدرن است — React، Vue، Node.js و TypeScript بدون این پایه، درک‌ناپذیرند؛ سوم، عادت‌های کوچک مثل استفاده از const و ===، در بلندمدت کیفیت کد شما را به‌طور محسوس بالا می‌برند.

اگر امروز می‌خواهید این ویژگی‌ها را در عمل تمرین کنید، سه کار کوچک پیشنهاد می‌کنم: یک تابع ساده که قبلاً با var و چسباندن رشته می‌نوشتید را با ES6 بازنویسی کنید و تفاوت را ببینید؛ یک فایل جاوااسکریپت را به چند ماژول بشکنید و از import/export استفاده کنید؛ و یک سناریوی واقعی از پروژه‌ی خودتان که با کد دفاعی پیچیده حل می‌شد را با optional chaining و nullish coalescing بازنویسی کنید. همین سه تمرین، ۹۰٪ قابلیت‌های ES6 را در ذهن شما زنده می‌کند. مسیر طبیعی بعدی، یادگیری Promise در جاوااسکریپت، async/await در جاوااسکریپت و fetch API در جاوااسکریپت است — این‌ها هم بخشی از ES6 به بعد هستند و در پروژه‌های واقعی جدانشدنی‌اند. اگر تجربه‌ای از کار با ES6 در پروژه‌های خودتان دارید — مخصوصاً اگر با یک ویژگی خاص مشکل داشته‌اید یا از یک الگوی غیرمنتظره نتیجه گرفته‌اید — در دیدگاه‌ها بنویسید؛ همین نکته‌های میدانی، برای خواننده‌ی بعدی از هر مستند رسمی ارزشمندتر است. ⚡