JavaScript מודרני
שיעור 16 — JavaScript מודרני
JavaScript היא שפת הדפדפן. שיעור זה מכסה ES6+, DOM manipulation, Event Loop, Closures, ו-async programming — כל הכלים לבניית אפליקציית Web אמיתית.
- ES6+: const/let, Arrow Functions, Destructuring, Spread, Optional Chaining
- DOM: querySelector, classList, addEventListener, Event Delegation
- Event Loop: Call Stack → Microtask Queue → Callback Queue
- Closures: פונקציה שסגורה על הסביבה שנוצרה בה
- Async: Callbacks (הבעיה) → Promises (.then/.catch) → async/await
- Fetch API: GET/POST, response.ok, JSON.stringify/parse
- localStorage: שמירה בדפדפן, אסור לסיסמאות/tokens
- ES Modules: import/export, default vs named
JavaScript מודרני — ES6+
ES6 (2015) שינתה את JavaScript לחלוטין. כל הקוד שתכתוב היום משתמש בתכונות אלו.
const, let, var
// var — ישן: Function scoped, hoisting מוזר. אל תשתמש!
var x = 1;
if (true) { var x = 2; } // אותו x!
console.log(x); // 2 — מפתיע!
// let — Block scoped (כמו Python)
let y = 1;
if (true) { let y = 2; } // y שונה בתוך block
console.log(y); // 1 — כצפוי
// const — Block scoped + לא ניתן להשמה מחדש
const PI = 3.14;
// PI = 3; // TypeError!
// ⚠️ const לא אומר "immutable"!
const arr = [1, 2, 3];
arr.push(4); // OK — שינוי תוכן מותר
// arr = [5]; // TypeError — השמה מחדש אסורה
// כלל: תמיד const. אם צריך לשנות — let. לעולם לא var.Arrow Functions
// פונקציה רגילה vs Arrow:
function add(a, b) { return a + b; }
const add = (a, b) => a + b; // one-liner: return implicit
const square = x => x * x; // param אחד: ללא ()
const greet = () => "Hello!"; // ללא params
// map/filter/reduce — שימוש נפוץ:
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2); // [2,4,6,8,10]
const evens = nums.filter(n => n%2===0); // [2,4]
const total = nums.reduce((acc,n)=>acc+n, 0); // 15! this ב-Arrow Functions — הטעות הנפוצה! ›
// Arrow function לא יוצרת this משלה — יורשת מהסביבה
const obj = {
name: "Bob",
greet: function() {
setTimeout(function() {
console.log(this.name); // undefined! this = window
}, 1000);
},
greetFixed: function() {
setTimeout(() => {
console.log(this.name); // "Bob" — arrow ירש this ✓
}, 1000);
}
};Destructuring, Spread, Optional Chaining
// Array Destructuring:
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]
// Object Destructuring:
const user = { name: "Alice", age: 30, role: "admin" };
const { name, age } = user;
const { name: userName, role = "user" } = user; // rename + default
// Spread Operator:
const arr2 = [...arr, 4, 5]; // copy + extend
const obj2 = { ...obj1, b: 99 }; // copy + override
// Optional Chaining — מונע "Cannot read property of undefined":
const user = null;
console.log(user?.address?.city); // undefined (לא throw!)
console.log(user?.getName?.()); // undefined
// Nullish Coalescing:
const port = config.port ?? 3000; // רק null/undefined → 3000
// (לא כמו || שגם 0 ו-"" גורמים ל-fallback)const arr=[1,2,3]; arr.push(4); console.log(arr.length);const {a: x = 10} = {b: 5}; — מה ערך x?console.log(typeof null);console.log(0 || "default");
console.log(0 ?? "default");DOM Manipulation ו-Events
JS + DOM = דפים דינמיים. JS יכול לשנות כל דבר בדף בזמן אמת — ללא רענון.
בחירת אלמנטים
document.getElementById("main") // by ID — מהיר
document.querySelector(".btn") // CSS selector — ראשון
document.querySelectorAll("p.text") // כולם → NodeList (static)
document.getElementsByClassName("card") // HTMLCollection (live!)
// ניווט:
el.parentElement // הורה
el.children // ילדים
el.closest(".card") // מחפש למעלה בעץשינוי DOM
el.textContent = "Hello"; // טקסט בטוח ✓
el.innerHTML = "<b>Hello</b>"; // מפרסר HTML — סכנת XSS!
el.classList.add("active");
el.classList.toggle("open"); // הוסף אם אין, הסר אם יש
el.setAttribute("href", "...");
el.style.color = "red"; // עדיף class togglingEvent Listeners + Bubbling
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick); // חייב reference!
function handleClick(event) {
event.preventDefault(); // מניעת ברירת מחדל
event.stopPropagation(); // עצירת Bubbling
console.log(event.target); // האלמנט שנלחץ
}
// Event Delegation — listener אחד לכל הילדים:
list.addEventListener("click", (e) => {
if (e.target.matches("li")) {
console.log("Clicked:", e.target.textContent);
}
}); // יעיל מ-listener על כל li בנפרד!
// Bubbling: click על btn → inner → outer → document
// stopPropagation() עוצר את העלייהdocument.querySelectorAll("p")?addEventListener("click", fn, { once: true })?for (var i=0; i<3; i++) {
setTimeout(() => console.log(i), 0);
}
// מצפים: 0 1 2
// מקבלים: ???== ל-===?Event Loop ו-Closures
JS היא Single-Threaded אבל לא חסומת. Event Loop מאפשר async. Closures הן אחת התכונות החזקות ביותר של JS.
מרכיבי Event Loop
| מרכיב | תפקיד | דוגמה |
|---|---|---|
| Call Stack | קוד סינכרוני רץ כאן | הפונקציה הנוכחית |
| Web APIs | משימות ב-background (דפדפן) | setTimeout, fetch |
| Microtask Queue | עדיפות גבוהה — לפני Callback | Promise.then, await |
| Callback Queue | callbacks שמחכים | setTimeout callback |
| Event Loop | Stack ריק? → Microtask → Callback | הלב של async |
console.log("1 — Start"); // Call Stack
setTimeout(() => console.log("2 — Timeout"), 0); // Callback Queue
Promise.resolve().then(() => console.log("3 — Promise")); // Microtask Queue
console.log("4 — End"); // Call Stack
// סדר הפלט:
// 1 — Start
// 4 — End
// 3 — Promise ← Microtask Queue לפני Callback!
// 2 — TimeoutClosures
// Closure = פונקציה שזוכרת את הסביבה שנוצרה בה
function makeCounter() {
let count = 0; // "נסתר" מבחוץ
return {
increment: () => ++count,
decrement: () => --count,
value: () => count
};
}
const counter = makeCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.value()); // 1 (count לא נגיש מבחוץ!)
// Closure בפועל — createAdder:
function createAdder(x) {
return function(y) { x += y; return x; };
}
const add = createAdder(10);
add(5); // 15 (x: 10→15)
add(3); // 18 (x: 15→18, x השתנה!)
const add2 = createAdder(0); // closure נפרד לחלוטין!
add2(7); // 7console.log('A');
setTimeout(() => console.log('B'), 100);
setTimeout(() => console.log('C'), 0);
Promise.resolve().then(() => console.log('D'));
console.log('G');מה סדר הפלט?const x = 1;
function outer() {
const x = 2;
return () => x;
}
console.log(outer()());const fn = () => arguments; try { fn() } catch(e) { console.log(e.constructor.name); }async function f() { await Promise.resolve(); return 42; } console.log(f());Async JavaScript — Promises ו-async/await
הבעיה: Callback Hell. הפתרון: Promises. הסינטקס הנוח: async/await. כל שלוש השיטות חיות יחד.
Promises
// Promise מצבים: Pending → Fulfilled / Rejected
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) resolve({ data: "result" });
else reject(new Error("Something failed"));
}, 1000);
});
promise
.then(data => console.log("Success:", data))
.catch(err => console.error("Error:", err))
.finally(() => console.log("Done (always runs)"));
// Promise Chaining — פתרון ל-Callback Hell:
getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.catch(err => console.error(err));
// Promise.all — מקביל, מחכה לכולם:
Promise.all([fetchUser(), fetchPosts(), fetchConfig()])
.then(([user, posts, config]) => { /* כולם הושלמו */ });async/await
// async/await = סינטקס סוכר על Promises
async function loadUserData(userId) {
try {
const user = await getUser(userId); // עוצרת עד resolve
const posts = await getPosts(user.id); // אחרי user
return posts;
} catch (err) {
console.error("Error:", err);
throw err; // re-throw אם צריך
}
}
// מקביל עם async/await:
async function parallel() {
const [user, config] = await Promise.all([getUser(1), getConfig()]);
// שניהם מבוצעים במקביל! לא כל אחד בתורו
}! await בלי async = SyntaxError ›
await חייב להיות בתוך async function. לא ניתן ב-top-level (אלא ב-ES modules). תיקון: עטוף ב-async IIFE: (async () => { const data = await fetch("..."); })()
Promise.resolve(1)
.then(v => v + 1)
.then(v => { throw v; })
.catch(v => v + 3)
.then(v => console.log(v));const p = new Promise((res, rej) => {
res(1); res(2); rej(3);
});
p.then(console.log).catch(console.log);async function loadAll() {
const user = await getUser(); // 1 שניה
const config = await getConfig(); // 1 שניה
return { user, config }; // סה"כ: 2 שניות!
}Fetch API, localStorage ו-סיכום
Fetch = HTTP מהדפדפן. localStorage = שמירה מקומית. ES Modules = ארגון קוד. כל אלה הם הבסיס לאפליקציה מלאה.
Fetch API
// GET Request:
async function getUsers() {
const response = await fetch("https://api.example.com/users");
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json(); // פרסור JSON
return data;
}
// POST Request:
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${getToken()}`,
},
body: JSON.stringify(userData), // object → JSON string
});
if (!response.ok) throw new Error("Request failed");
return response.json();
}localStorage
// שמירה — ערכים חייבים להיות strings
localStorage.setItem("theme", "dark");
localStorage.setItem("user", JSON.stringify({ name: "Alice" }));
// קריאה
const theme = localStorage.getItem("theme"); // "dark" or null
const user = JSON.parse(localStorage.getItem("user") || "null");
// מחיקה
localStorage.removeItem("theme");
localStorage.clear(); // מחיקת הכלES Modules
// utils.js — יצוא
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export default class Calculator { /* ... */ }
// main.js — ייבוא
import Calculator from "./utils.js"; // default import
import { PI, add } from "./utils.js"; // named imports
import { add as sum } from "./utils.js"; // alias
import * as Utils from "./utils.js"; // הכל
// ב-HTML: <script type="module" src="main.js"></script>
// type="module": defer אוטומטי, strict mode, own scope- ES6+: const/let (לא var!), Arrow functions, Destructuring, Spread, Optional Chaining (?.), Nullish (??)
- DOM: querySelector, classList, addEventListener, Event Delegation, Bubbling
- Event Loop: Microtasks (Promise.then) לפני Callbacks (setTimeout)!
- Closures: פונקציה שסגורה על scope יצירתה. var בלולאה = באג. let = תיקון.
- Async: Callback Hell → Promises (.then/.catch) → async/await (קריא)
- Fetch: תמיד בדוק response.ok. POST עם JSON.stringify. לא throw על 404.
- localStorage: JSON.stringify/parse. אסור ל-tokens! HttpOnly Cookie = נכון.
- ES Modules: import/export, default vs named, dynamic import
const obj={x:1}; const copy={...obj}; copy.x=99; console.log(obj.x);const arr=[1,2,3]; const [, second]=arr; console.log(second);localStorage.setItem("key", {a:1}); מה נשמר?const result = [1, [2, [3]]].flat(Infinity);תרגול נוסף — JavaScript: Event Loop ו-Promises
שאלות העשרה נוספות לחיזוק החומר של השיעור — באותו פורמט בחירה של הקורס.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");async function לפונקציה רגילה?async function getAll(ids) {
const results = [];
for (const id of ids) {
const data = await fetch(`/api/${id}`);
results.push(await data.json());
}
return results;
}אתגר מעשי — JavaScript: אירועים ו-Async
שיא השיעור: לממש את הלוגיקה שמאחורי ה-Web — פרסור, אירועים, state ואבטחה. פתרו את החובה ואז את האתגר; ההגשה נבדקת אוטומטית מול מקרי-קצה נסתרים.
שאלות תרגול ותשובות (10)
שאלה 1 · מה יודפס?
const arr = [1, 2, 3]; arr.push(4); console.log(arr.length);
- א. 4
- ב. 3
- ג. שגיאה — arr הוא const
- ד. undefined
const אוסר השמה מחדש, לא שינוי תוכן. push מוסיף איבר → 4.
שאלה 2 · מה ערך x?
const { a: x = 10 } = { b: 5 };- א. 5
- ב. 10
- ג. undefined
- ד. שגיאה
המפתח a לא קיים באובייקט → נכנסת ברירת המחדל 10.
שאלה 3 · מה מחזיר typeof null?
console.log(typeof null);
- א. "object"
- ב. "null"
- ג. "undefined"
- ד. "boolean"
באג היסטורי שנשמר לתאימות לאחור — typeof null הוא תמיד "object".
שאלה 4 · מה יודפס בשתי השורות?
console.log(0 || "default"); console.log(0 ?? "default");
- א. default, default
- ב. default, 0
- ג. 0, 0
- ד. 0, default
|| בודק falsy (0 נופל), ?? בודק רק null/undefined (0 נשמר).
שאלה 5 · למה עדיף textContent על innerHTML כשמציגים קלט משתמש?
- א. הוא מהיר יותר
- ב. הוא כותב טקסט בלבד ולא מריץ HTML/סקריפט
- ג. הוא תומך בעברית
- ד. הוא שומר על עיצוב CSS
innerHTML מפרסר HTML → פתח ל-XSS. textContent מכניס מחרוזת בלבד.
שאלה 6 · מהו Event Delegation?
- א. רישום מאזין לכל אלמנט בנפרד
- ב. ביטול בועות האירוע
- ג. מאזין אחד על ההורה שמזהה את היעד דרך event.target
- ד. הרצת אירועים בתוך Web Worker
מאזין אחד על ההורה חוסך זיכרון ועובד גם על אלמנטים שנוספו אחרי הטעינה.
שאלה 7 · מה סדר ההדפסה?
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");- א. A D C B
- ב. A B C D
- ג. A C D B
- ד. A D B C
קוד סינכרוני (A, D) → Microtasks (Promise: C) → Macrotasks (setTimeout: B). 🎯 דגש למבחן — מעקב אחרי ה-Event Loop: ציירו בטיוטה 3 עמודות — Call Stack | Microtasks | Macrotasks. כל שורה נכנסת לעמודה שלה, ואז שולפים לפי הסדר: קודם כל ה-Stack, אחריו כל ה-Microtasks, ורק בסוף Macrotask אחד.
שאלה 8 · מה יודפס?
function counter() {
let c = 0;
return () => ++c;
}
const inc = counter();
inc(); inc();
console.log(inc());- א. 1
- ב. 3
- ג. undefined
- ד. שגיאה — c לא בטווח
Closure: הפונקציה המוחזרת שומרת גישה ל-c גם אחרי שה-counter הסתיים → 1,2,3.
שאלה 9 · למה התנאי הזה לא מספיק כדי לזהות כישלון?
const res = await fetch(url); const data = await res.json();
- א. צריך גם try/catch על json בלבד
- ב. fetch לא זורק שגיאה על 404/500 — חייבים לבדוק res.ok
- ג. fetch מחזיר מחרוזת ולא אובייקט
- ד. חייבים להוסיף method: "GET"
fetch נדחה רק על תקלת רשת. תגובת 404/500 מגיעה כ-resolved — לכן בודקים res.ok.
שאלה 10 · מה נכון לגבי localStorage?
- א. הוא מוצפן על ידי הדפדפן
- ב. הוא נמחק בסגירת הלשונית
- ג. הוא נגיש ל-JavaScript ולכן אסור לשמור בו טוקנים
- ד. הוא נשלח אוטומטית בכל בקשה לשרת
localStorage נשמר כטקסט גלוי ונגיש מכל סקריפט בדף — XSS גונב אותו מיד. sessionStorage הוא זה שנמחק בסגירת הלשונית.