Let و Const ونطاق الكتلة
أحد أهم التحسينات في ES6 هو إدخال let و const لإعلانات المتغيرات. في هذا الدرس، سنستكشف كيف يختلفان عن var، ونفهم نطاق الكتلة، ونتعلم أفضل الممارسات لإعلانات المتغيرات الحديثة.
var مقابل let مقابل const
كان لدى ES5 فقط var لإعلانات المتغيرات، والتي كانت لها قواعد نطاق مربكة. قدمت ES6 let و const لحل هذه المشكلات:
var (ES5 - نطاق الدالة):
var name = "John";
name = "Jane"; // ✓ يمكن إعادة الإسناد
var name = "Bob"; // ✓ يمكن إعادة الإعلان (إشكالي!)
let (ES6+ - نطاق الكتلة):
let age = 25;
age = 26; // ✓ يمكن إعادة الإسناد
let age = 27; // ✗ لا يمكن إعادة الإعلان (خطأ)
const (ES6+ - نطاق الكتلة):
const PI = 3.14159;
PI = 3.14; // ✗ لا يمكن إعادة الإسناد (خطأ)
const PI = 3.14; // ✗ لا يمكن إعادة الإعلان (خطأ)
الفرق الرئيسي: var محددة بنطاق الدالة، بينما let و const محددة بنطاق الكتلة (محدودة بـ {الأقواس المعقوفة}).
نطاق الكتلة مقابل نطاق الدالة
فهم النطاق أمر بالغ الأهمية لكتابة كود خالٍ من الأخطاء:
نطاق الدالة (var):
function example() {
if (true) {
var x = 10;
}
console.log(x); // 10 - يمكن الوصول إليها خارج كتلة if
}
نطاق الكتلة (let/const):
function example() {
if (true) {
let y = 20;
const z = 30;
}
console.log(y); // خطأ: y غير معرف
console.log(z); // خطأ: z غير معرف
}
يمنع نطاق الكتلة تسرب المتغيرات خارج سياقها المقصود:
الحلقات مع 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 (ينشئ ربطاً جديداً في كل تكرار)
نصيحة: استخدم دائماً let أو const في الحلقات. كل تكرار ينشئ نطاق كتلة جديد، مما يمنع أخطاء الإغلاق الشائعة.
فروقات سلوك الرفع
الرفع هو سلوك JavaScript لنقل الإعلانات إلى أعلى نطاقها:
رفع var (يتم تهيئته بـ undefined):
console.log(name); // undefined (ليس خطأ!)
var name = "John";
console.log(name); // "John"
ما يعادل:
var name; // تم رفعه وتهيئته بـ undefined
console.log(name);
name = "John";
console.log(name);
رفع let/const (المنطقة الزمنية الميتة):
console.log(age); // ReferenceError: لا يمكن الوصول قبل التهيئة
let age = 25;
console.log(PI); // ReferenceError: لا يمكن الوصول قبل التهيئة
const PI = 3.14;
مهم: يتم رفع let و const لكن لا يتم تهيئتهما، مما ينشئ "منطقة زمنية ميتة" من بداية الكتلة حتى الوصول إلى الإعلان.
المنطقة الزمنية الميتة (TDZ)
المنطقة الزمنية الميتة هي الفترة بين الدخول إلى النطاق وإعلان المتغير:
{
// تبدأ TDZ هنا لـ 'name'
console.log(name); // ReferenceError
let name = "John"; // تنتهي TDZ، تم تهيئة name
console.log(name); // "John" - يعمل بشكل جيد
}
تمنع المنطقة الزمنية الميتة استخدام المتغيرات قبل إعلانها، مما يكشف عن الأخطاء المحتملة:
function example(x = y, y = 2) {
return [x, y];
}
example(); // ReferenceError: لا يمكن الوصول إلى 'y' قبل التهيئة
// y في TDZ عند استخدامها كقيمة افتراضية لـ x
متى تستخدم let مقابل const
أفضل ممارسات JavaScript الحديثة للاختيار بين let و const:
استخدم const (الخيار الافتراضي):
✓ للقيم التي لن يتم إعادة إسنادها
✓ لمراجع الكائن/المصفوفة (حتى لو تغيرت المحتويات)
✓ لتعبيرات الدالة
✓ يجعل الكود أكثر قابلية للتنبؤ وأسهل للفهم
استخدم let (عند الضرورة):
✓ للمتغيرات التي سيتم إعادة إسنادها
✓ لعدادات الحلقات
✓ للمتغيرات التراكمية
✓ عندما تحتاج إلى إعادة إسناد المرجع بالكامل
أمثلة عملية:
const للكائنات (يمكن تغيير المحتويات):
const user = { name: "John" };
user.name = "Jane"; // ✓ يعمل - تعديل المحتويات
user.age = 25; // ✓ يعمل - إضافة خصائص
user = {}; // ✗ خطأ - لا يمكن إعادة إسناد المرجع
const للمصفوفات (يمكن تغيير المحتويات):
const numbers = [1, 2, 3];
numbers.push(4); // ✓ يعمل - تعديل المحتويات
numbers[0] = 10; // ✓ يعمل - تغيير العناصر
numbers = []; // ✗ خطأ - لا يمكن إعادة إسناد المرجع
let لإعادة الإسناد:
let count = 0;
count++; // ✓ يعمل - إعادة إسناد القيمة
count = 100; // ✓ يعمل - إعادة إسناد القيمة
تذكر: const يمنع إعادة إسناد المتغير نفسه، وليس تغيير محتوياته. يمكن تعديل الكائنات والمصفوفات المُعلنة بـ const.
أفضل الممارسات لإعلانات المتغيرات
✓ افعل:
1. استخدم const بشكل افتراضي
2. استخدم let فقط عندما تحتاج إلى إعادة الإسناد
3. لا تستخدم var أبداً في JavaScript الحديثة
4. أعلن المتغيرات في أعلى نطاقها
5. استخدم أسماء ذات معنى ووصفية
✗ لا تفعل:
1. لا تستخدم var (يسبب ارتباك النطاق)
2. لا تعد إسناد متغيرات const
3. لا تعلن عدة متغيرات في سطر واحد
4. لا تستخدم المتغيرات قبل الإعلان
5. لا تستخدم أسماء من حرف واحد (باستثناء i، j في الحلقات)
المزالق الشائعة وكيفية تجنبها
المزلق 1: 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
المزلق 2: الخلط بين const وعدم القابلية للتغيير
// const لا يجعل الكائنات غير قابلة للتغيير:
const config = { api: "https://api.example.com" };
config.api = "https://new-api.example.com"; // يعمل!
// لجعلها غير قابلة للتغيير، استخدم Object.freeze():
const config = Object.freeze({ api: "https://api.example.com" });
config.api = "https://new-api.example.com"; // يفشل بصمت (الوضع الصارم: خطأ)
المزلق 3: متغيرات عامة عرضية
function example() {
x = 10; // ينشئ عن طريق الخطأ متغيراً عاماً!
}
// الحل: استخدم let/const والوضع الصارم
"use strict";
function example() {
let x = 10; // متغير محدد النطاق بشكل صحيح
}
تمرين الممارسة:
صحح هذا الكود باستبدال var بـ let/const بشكل مناسب:
var PI = 3.14159;
var radius = 5;
var area = PI * radius * radius;
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log("Loop: " + i);
}, 100);
}
var user = { name: "John" };
user = { name: "Jane" };
الحل:
const PI = 3.14159; // لن يتغير أبداً
let radius = 5; // قد يتم إعادة إسناده
const area = PI * radius * radius; // قيمة محسوبة
for (let i = 0; i < 3; i++) { // استخدم let لعداد الحلقة
setTimeout(function() {
console.log("Loop: " + i);
}, 100);
}
let user = { name: "John" }; // سيتم إعادة إسناده
user = { name: "Jane" }; // تحدث إعادة الإسناد
سيناريوهات العالم الحقيقي
السيناريو 1: كائنات التكوين
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3
};
// يمكن تعديل الخصائص، لكن المرجع يبقى ثابتاً
السيناريو 2: معالجات الأحداث
const button = document.querySelector("#myButton");
button.addEventListener("click", () => {
console.log("Button clicked!");
});
// const يمنع إعادة الإسناد العرضي لمرجع button
السيناريو 3: عدادات الحلقات
for (let i = 0; i < items.length; i++) {
const item = items[i]; // const داخل الحلقة
processItem(item);
}
// كل تكرار يحصل على روابط i و item جديدة
الملخص
في هذا الدرس، تعلمت:
var محددة بنطاق الدالة ولها سلوك رفع مربك
let و const محددة بنطاق الكتلة (محدودة بـ {الأقواس المعقوفة})
const تمنع إعادة الإسناد لكن تسمح بتغيير المحتويات
- المنطقة الزمنية الميتة تمنع استخدام المتغيرات قبل الإعلان
- استخدم
const بشكل افتراضي، let عند الحاجة إلى إعادة الإسناد
- لا تستخدم
var أبداً في JavaScript الحديثة
- نطاق الكتلة يمنع تسرب المتغيرات وأخطاء الإغلاق
التالي: في الدرس التالي، سنستكشف الدوال السهمية وكيف تبسط بناء جملة الدالة بينما تحل مشكلة ربط this!