4.1 קלוז׳רים וסקופ פתרון
פתרון - קלוז׳רים וסקופ¶
תרגיל 1 - חידות scope chain¶
חידה א:¶
let x = 1;
function a() {
let x = 2;
function b() {
console.log(x);
}
return b;
}
const fn = a();
fn(); // 2
תשובה: 2 - הפונקציה b רואה את x מהסקופ שבו היא הוגדרה (בתוך a), לא מהסקופ הגלובלי. זה סקופ לקסיקלי.
חידה ב:¶
let count = 0;
function increment() {
count++;
console.log(count);
}
function reset() {
let count = 0; // this is a NEW local variable, not the global one
console.log(count);
}
increment(); // 1
increment(); // 2
reset(); // 0 (local count, doesn't affect the global one)
increment(); // 3 (global count continues from 2)
תשובה: 1, 2, 0, 3 - הפונקציה reset יוצרת משתנה count חדש ומקומי - היא לא משנה את הגלובלי.
חידה ג:¶
function outer() {
let x = 10;
function middle() {
let y = 20;
function inner() {
let z = 30;
console.log(x + y + z);
}
x = 100; // modifies x before inner() runs
inner();
}
middle();
}
outer(); // 150
תשובה: 150 - כי x שונה ל-100 לפני שנקראה inner. ה-closure שומר הפניה למשתנה, לא עותק של הערך. אז 100 + 20 + 30 = 150.
תרגיל 2 - מונה עם closure¶
function createCounter(initialValue = 0) {
let value = initialValue;
return {
increment() {
return ++value;
},
decrement() {
return --value;
},
getValue() {
return value;
},
reset() {
value = initialValue;
}
};
}
// test
const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.decrement()); // 11
console.log(counter.getValue()); // 11
counter.reset();
console.log(counter.getValue()); // 10
// can't access value directly
console.log(counter.value); // undefined
תרגיל 3 - פונקציית memoize¶
function memoize(fn) {
const cache = new Map();
function memoized(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
}
// bonus: method to clear the cache
memoized.cache = {
clear() {
cache.clear();
}
};
return memoized;
}
// test
function slowSquare(n) {
console.log("Computing...");
return n * n;
}
const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // "Computing..." -> 25
console.log(fastSquare(5)); // 25 (from cache)
console.log(fastSquare(3)); // "Computing..." -> 9
console.log(fastSquare(5)); // 25 (from cache)
fastSquare.cache.clear();
console.log(fastSquare(5)); // "Computing..." -> 25 (cache was cleared)
תרגיל 4 - משתנים פרטיים¶
function createPasswordManager() {
let password = null;
return {
setPassword(newPassword) {
if (newPassword.length < 8) {
return false;
}
password = newPassword;
return true;
},
checkPassword(attempt) {
return attempt === password;
},
getHint() {
if (!password) {
return "No password set";
}
return password.slice(0, 2) + "*".repeat(password.length - 2);
}
};
}
// test
const pm = createPasswordManager();
console.log(pm.setPassword("abc")); // false
console.log(pm.setPassword("mySecret123")); // true
console.log(pm.checkPassword("wrong")); // false
console.log(pm.checkPassword("mySecret123")); // true
console.log(pm.getHint()); // "my*********"
// password is not accessible
console.log(pm.password); // undefined
תרגיל 5 - closure בלולאה¶
חלק א - זיהוי הבאג¶
const buttons = [];
for (var i = 0; i < 5; i++) {
buttons.push({
label: `Button ${i}`,
click: function() {
console.log(`Clicked button ${i}`);
}
});
}
buttons[0].click(); // "Clicked button 5"
buttons[2].click(); // "Clicked button 5"
buttons[4].click(); // "Clicked button 5"
כל הכפתורים מדפיסים "Clicked button 5" כי var הוא function-scoped. יש רק i אחד, וכשהלולאה מסתיימת הוא שווה 5. כל ה-closures מצביעים על אותו i.
שימו לב ש-label נכון (Button 0, Button 1, ...) כי template literal מחושב מיד בזמן ה-push, אבל ה-closure של click לא מופעל עד שלוחצים.
חלק ב - תיקון עם let¶
const buttons = [];
for (let i = 0; i < 5; i++) {
buttons.push({
label: `Button ${i}`,
click: function() {
console.log(`Clicked button ${i}`);
}
});
}
buttons[0].click(); // "Clicked button 0"
buttons[2].click(); // "Clicked button 2"
buttons[4].click(); // "Clicked button 4"
חלק ב - תיקון עם IIFE¶
const buttons = [];
for (var i = 0; i < 5; i++) {
(function(j) {
buttons.push({
label: `Button ${j}`,
click: function() {
console.log(`Clicked button ${j}`);
}
});
})(i);
}
buttons[0].click(); // "Clicked button 0"
buttons[2].click(); // "Clicked button 2"
buttons[4].click(); // "Clicked button 4"
תרגיל 6 - תבנית המודול - module pattern¶
const TaskManager = (function() {
let tasks = [];
let nextId = 1;
return {
addTask(title) {
const task = {
id: nextId++,
title,
done: false
};
tasks.push(task);
return task;
},
completeTask(id) {
const task = tasks.find(t => t.id === id);
if (task) {
task.done = true;
return true;
}
return false;
},
removeTask(id) {
const index = tasks.findIndex(t => t.id === id);
if (index !== -1) {
tasks.splice(index, 1);
return true;
}
return false;
},
getTasks() {
return tasks.map(t => ({ ...t }));
},
getPending() {
return tasks
.filter(t => !t.done)
.map(t => ({ ...t }));
},
getStats() {
const done = tasks.filter(t => t.done).length;
return {
total: tasks.length,
done,
pending: tasks.length - done
};
}
};
})();
// test
TaskManager.addTask("Learn closures");
TaskManager.addTask("Practice exercises");
TaskManager.addTask("Build project");
console.log(TaskManager.getStats());
// { total: 3, done: 0, pending: 3 }
TaskManager.completeTask(1);
console.log(TaskManager.getPending().length); // 2
console.log(TaskManager.getStats());
// { total: 3, done: 1, pending: 2 }
TaskManager.removeTask(2);
console.log(TaskManager.getStats());
// { total: 2, done: 1, pending: 1 }
// tasks and nextId are not accessible
console.log(TaskManager.tasks); // undefined
תרגיל 7 - createMultiplier ושרשרת פונקציות¶
חלק א¶
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(double(10)); // 20
חלק ב¶
function compose(...fns) {
return function(value) {
// apply functions from right to left
return fns.reduceRight((result, fn) => fn(result), value);
};
}
const double = createMultiplier(2);
const addOne = (x) => x + 1;
const square = (x) => x * x;
const transform = compose(addOne, double, square);
console.log(transform(3)); // square(3)=9 -> double(9)=18 -> addOne(18)=19
console.log(transform(5)); // square(5)=25 -> double(25)=50 -> addOne(50)=51
ה-closure ב-compose: הפונקציה המוחזרת סוגרת על המערך fns - היא זוכרת אילו פונקציות הועברו ל-compose גם אחרי ש-compose סיימה לרוץ. כל קריאה ל-transform ניגשת ל-fns דרך ה-closure.