أتقن كيف تلتقط الإغلاقات المتغيرات لتغلّف الحالة وتتجنّب مزالق النطاق.
افتح هذا الدرس في Kodokonالإغلاق (closure) ليس حيلة غريبة: إنه الآلية التي تحتفظ بها الدالة بالوصول إلى النطاق المعجمي الذي عُرّفت فيه، لا الذي استُدعيت فيه. المحرك لا ينسخ القيم: بل يحتفظ بمرجع حي للبيئة المعجمية. لذلك ينتج عن استدعاءين لنفس المصنع بيئتان مستقلتان، وهذا ما يجعل الإغلاقات أخف أداة تغليف في اللغة.
function createCounter() {
let count = 0;
return {
increment: () => ++count,
reset: () => { count = 0; },
};
}
const counterA = createCounter();
const counterB = createCounter();
counterA.increment();
counterA.increment();
console.log(counterA.increment()); // 3
console.log(counterB.increment()); // 1المزلق الكلاسيكي في الواقع العملي: يرتبط var بالدالة المحيطة لا بالكتلة. وداخل حلقة، يلتقط كل إغلاق حينئذٍ نفس المتغير. أما مع let، فتنشئ كل تكرارة بيئة معجمية جديدة، فيصبح كل التقاط منفصلا. أضف إلى ذلك المنطقة الميتة الزمنية (TDZ): يوجد متغير let أو const بمجرد الدخول إلى الكتلة، لكنه يرمي ReferenceError إن قرأته قبل تصريحه.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3, 3, 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0); // 0, 1, 2
}من ناحية الأنماط، الإغلاقات أساس التذكير (memoization) ونمط الوحدة. مقارنة بالصنف، يمنحك الإغلاق خصوصية حقيقية بلا this ولا نماذج أولية، لكن بكلفة استهلاك ذاكرة أعلى قليلا حين تنشئ آلاف المثيلات: إذ يحمل كل مثيل دواله الخاصة.
function memoize(fn) {
const cache = new Map();
return function (key) {
if (cache.has(key)) return cache.get(key);
const result = fn(key);
cache.set(key, result);
return result;
};
}
const square = (n) => n * n;
const fastSquare = memoize(square);
console.log(fastSquare(9)); // computed
console.log(fastSquare(9)); // read from the cachefor (var i = ...)، ماذا تطبع ثلاثة استدعاءات setTimeout أُنشئت داخل جسم الحلقة؟createCounter بقاء count خاصا؟