לדלג לתוכן

2.9 דסטרקצ׳רינג וספרד הרצאה

דסטרקצ'רינג וספרד - Destructuring and Spread

דסטרקצ'רינג הוא תחביר מיוחד שמאפשר "לפרק" מערכים ואובייקטים לתוך משתנים בודדים.
זה נשמע מסובך, אבל בפועל זה עושה את הקוד הרבה יותר קריא ונקי.

דסטרקצ'רינג הוא פיצ'ר שתשתמשו בו כל יום - במיוחד ב-React.


דסטרקצ'רינג של מערכים - Array Destructuring

הבסיס

במקום לגשת לאלמנטים לפי אינדקס, אפשר לפרק את המערך ישירות למשתנים:

const colors = ["red", "green", "blue"];

// without destructuring
const first = colors[0];
const second = colors[1];
const third = colors[2];

// with destructuring
const [red, green, blue] = colors;

console.log(red);   // "red"
console.log(green); // "green"
console.log(blue);  // "blue"

בפייתון זה מקביל ל-tuple unpacking:

// Python equivalent:
// red, green, blue = ["red", "green", "blue"]

דילוג על אלמנטים

const numbers = [1, 2, 3, 4, 5];

// skip the second element
const [first, , third] = numbers;
console.log(first); // 1
console.log(third); // 3

// skip first two
const [, , thirdNum] = numbers;
console.log(thirdNum); // 3

ערכי ברירת מחדל - Default Values

const arr = [1];

const [a, b] = arr;
console.log(a); // 1
console.log(b); // undefined

// with defaults
const [x = 0, y = 0, z = 0] = arr;
console.log(x); // 1
console.log(y); // 0
console.log(z); // 0

החלפת ערכי משתנים - Swapping

טריק נחמד שמאפשר להחליף ערכים בלי משתנה זמני:

let a = 1;
let b = 2;

// without destructuring
// let temp = a;
// a = b;
// b = temp;

// with destructuring - one line!
[a, b] = [b, a];

console.log(a); // 2
console.log(b); // 1

בפייתון: a, b = b, a - אותו עיקרון בדיוק.


דסטרקצ'רינג של אובייקטים - Object Destructuring

הבסיס

const person = { name: "Alice", age: 25, city: "Tel Aviv" };

// without destructuring
const name = person.name;
const age = person.age;

// with destructuring
const { name, age, city } = person;

console.log(name); // "Alice"
console.log(age);  // 25
console.log(city); // "Tel Aviv"

שימו לב להבדל ממערכים: במערכים חשוב הסדר, באובייקטים חשוב השם. אפשר לפרק בכל סדר:

const { city, name } = person; // works - order doesn't matter

שינוי שם - Alias

לפעמים רוצים לתת למשתנה שם אחר מהמפתח באובייקט:

const person = { name: "Alice", age: 25 };

const { name: fullName, age: years } = person;

console.log(fullName); // "Alice"
console.log(years);    // 25
// console.log(name);  // Error - name is not defined!

שימושי כשיש התנגשות שמות:

const { name: personName } = person;
const { name: companyName } = company;

ערכי ברירת מחדל

const person = { name: "Alice" };

const { name, age = 0, city = "Unknown" } = person;

console.log(name); // "Alice"
console.log(age);  // 0 (default)
console.log(city); // "Unknown" (default)

שילוב alias עם ברירת מחדל

const { name: fullName = "Anonymous", age: years = 0 } = {};

console.log(fullName); // "Anonymous"
console.log(years);    // 0

דסטרקצ'רינג מקונן - Nested Destructuring

const user = {
  name: "Alice",
  address: {
    street: "Herzl 10",
    city: "Tel Aviv",
    country: "Israel"
  }
};

// nested destructuring
const { name, address: { city, country } } = user;

console.log(name);    // "Alice"
console.log(city);    // "Tel Aviv"
console.log(country); // "Israel"
// console.log(address); // Error! address is not defined as a variable

אם רוצים גם את address כמשתנה וגם לפרק אותו:

const { name, address, address: { city } } = user;

console.log(address); // { street: "Herzl 10", city: "Tel Aviv", country: "Israel" }
console.log(city);    // "Tel Aviv"

דסטרקצ'רינג בפרמטרים של פונקציות

זה אחד השימושים הכי חשובים, במיוחד ב-React.

בלי דסטרקצ'רינג

function greet(person) {
  console.log(`Hello ${person.name}, you are ${person.age} years old`);
}

עם דסטרקצ'רינג

function greet({ name, age }) {
  console.log(`Hello ${name}, you are ${age} years old`);
}

greet({ name: "Alice", age: 25 }); // "Hello Alice, you are 25 years old"

עם ברירות מחדל

function createUser({ name, age = 18, role = "user" } = {}) {
  return { name, age, role };
}

createUser({ name: "Alice" });          // { name: "Alice", age: 18, role: "user" }
createUser({ name: "Bob", age: 30 });   // { name: "Bob", age: 30, role: "user" }
createUser();                            // { name: undefined, age: 18, role: "user" }

ה-= {} בסוף מבטיח שהפונקציה לא תקרוס אם קוראים לה בלי ארגומנט.

דסטרקצ'רינג עם מערכים בפרמטרים

function getFirstAndLast([first, ...rest]) {
  const last = rest[rest.length - 1];
  return { first, last };
}

console.log(getFirstAndLast([1, 2, 3, 4, 5]));
// { first: 1, last: 5 }

ב-React תשתמשו בדסטרקצ'רינג של props כל הזמן:

// in React:
// function UserCard({ name, age, avatar }) {
//   return <div>{name} ({age})</div>;
// }

אופרטור Rest - שאר האלמנטים

אופרטור ה-rest (...) אוסף את "השאר" לתוך משתנה אחד.

Rest במערכים

const [first, second, ...rest] = [1, 2, 3, 4, 5];

console.log(first);  // 1
console.log(second); // 2
console.log(rest);   // [3, 4, 5]

Rest באובייקטים

const person = { name: "Alice", age: 25, city: "Tel Aviv", email: "alice@test.com" };

const { name, ...rest } = person;

console.log(name); // "Alice"
console.log(rest); // { age: 25, city: "Tel Aviv", email: "alice@test.com" }

שימושי מאוד כשרוצים להפריד מאפיינים מסוימים מהשאר:

// remove a property from an object (without mutating)
const { email, ...personWithoutEmail } = person;
console.log(personWithoutEmail); // { name: "Alice", age: 25, city: "Tel Aviv" }

Rest בפרמטרים של פונקציות

function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3));       // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

בפייתון זה מקביל ל:

// Python equivalent:
// first, second, *rest = [1, 2, 3, 4, 5]
//
// def sum(*numbers):
//     return sum(numbers)
//
// name, **rest = not possible in Python for dicts
// (Python uses **kwargs for function parameters)

אופרטור Spread - פיזור

אופרטור ה-spread (...) הוא ההפך מ-rest - הוא "מפזר" אלמנטים.

Spread עם מערכים

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// combine arrays
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// add elements at specific positions
const withMiddle = [...arr1, 99, ...arr2];
console.log(withMiddle); // [1, 2, 3, 99, 4, 5, 6]

// copy an array
const copy = [...arr1];
copy.push(4);
console.log(arr1); // [1, 2, 3] - original unchanged
console.log(copy); // [1, 2, 3, 4]

Spread עם אובייקטים

const person = { name: "Alice", age: 25 };
const contact = { email: "alice@test.com", phone: "050-1234567" };

// merge objects
const fullPerson = { ...person, ...contact };
console.log(fullPerson);
// { name: "Alice", age: 25, email: "alice@test.com", phone: "050-1234567" }

// override properties
const updated = { ...person, age: 26 };
console.log(updated); // { name: "Alice", age: 26 }
console.log(person);  // { name: "Alice", age: 25 } - original unchanged

Spread כארגומנטים לפונקציה

const numbers = [5, 2, 8, 1, 9];

// Math.max expects individual arguments, not an array
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1

// equivalent to:
console.log(Math.max(5, 2, 8, 1, 9)); // 9

בפייתון:

// Python equivalent:
// combined = [*arr1, *arr2]
// full_person = {**person, **contact}
// max(*numbers)

Rest מול Spread - מה ההבדל?

אותו סינטקס (...), אבל שימוש שונה:

// SPREAD - spreading elements OUT (right side / argument)
const copy = [...original];          // spreading into new array
const merged = { ...obj1, ...obj2 }; // spreading into new object
Math.max(...numbers);                 // spreading as function arguments

// REST - collecting elements IN (left side / parameter)
const [first, ...rest] = arr;        // collecting remaining into array
const { name, ...others } = obj;    // collecting remaining into object
function sum(...nums) {}             // collecting arguments into array

כלל אצבע:
- בצד ימין (ערך) = spread (פיזור)
- בצד שמאל (הגדרה) = rest (איסוף)


העתקה רדודה מול העתקה עמוקה - Shallow vs Deep Copy

העתקה רדודה - Shallow Copy

Spread ו-Object.assign יוצרים העתקה רדודה - הם מעתיקים רק את הרמה הראשונה:

const original = {
  name: "Alice",
  address: { city: "Tel Aviv", street: "Herzl 10" }
};

// shallow copy
const copy = { ...original };

copy.name = "Bob";           // doesn't affect original
copy.address.city = "Haifa"; // DOES affect original!

console.log(original.name);         // "Alice" - OK
console.log(original.address.city); // "Haifa" - changed!

למה? כי ב-shallow copy, אובייקטים מקוננים לא מועתקים - רק ההפניה שלהם מועתקת. שני האובייקטים מצביעים על אותו אובייקט פנימי.

// same problem with arrays
const matrix = [[1, 2], [3, 4]];
const matrixCopy = [...matrix];

matrixCopy[0][0] = 99;
console.log(matrix[0][0]); // 99 - changed!

העתקה עמוקה - Deep Copy

const original = {
  name: "Alice",
  address: { city: "Tel Aviv", street: "Herzl 10" },
  hobbies: ["reading", "coding"]
};

// modern way - structuredClone (recommended)
const deepCopy = structuredClone(original);

deepCopy.address.city = "Haifa";
deepCopy.hobbies.push("swimming");

console.log(original.address.city); // "Tel Aviv" - not affected!
console.log(original.hobbies);      // ["reading", "coding"] - not affected!

// old trick - JSON parse/stringify
const deepCopy2 = JSON.parse(JSON.stringify(original));

JSON.parse(JSON.stringify(...)) עובד ברוב המקרים, אבל יש לו מגבלות:
- לא עובד עם פונקציות
- לא עובד עם Date objects (ממיר למחרוזת)
- לא עובד עם undefined (מוחק את המאפיין)
- לא עובד עם Map, Set, RegExp

structuredClone הוא הפתרון המודרני והמומלץ. הוא עובד עם רוב הטיפוסים (אבל לא עם פונקציות).

בפייתון:

// Python equivalent:
// import copy
// shallow = copy.copy(original)    -> { ...original }
// deep = copy.deepcopy(original)   -> structuredClone(original)

דוגמאות מעשיות

חילוץ ערכים מתשובת API

const apiResponse = {
  status: 200,
  data: {
    user: { id: 1, name: "Alice", email: "alice@test.com" },
    token: "abc123"
  },
  timestamp: "2024-01-01"
};

const { data: { user: { name, email }, token } } = apiResponse;
console.log(name);  // "Alice"
console.log(email); // "alice@test.com"
console.log(token); // "abc123"

עדכון אובייקט בצורה immutable

const state = {
  user: { name: "Alice", age: 25 },
  settings: { theme: "dark", language: "he" },
  count: 0
};

// update just the count (immutable)
const newState = { ...state, count: state.count + 1 };

// update nested object (immutable)
const newState2 = {
  ...state,
  user: { ...state.user, age: 26 }
};

// the original is unchanged
console.log(state.count);    // 0
console.log(state.user.age); // 25

זה הדפוס המרכזי לעדכון state ב-React.

הסרת מאפיין מאובייקט

const user = { name: "Alice", password: "secret", email: "alice@test.com" };

// remove password without mutating
const { password, ...safeUser } = user;
console.log(safeUser); // { name: "Alice", email: "alice@test.com" }

מיזוג עם ברירות מחדל

function createConfig(userConfig) {
  const defaults = {
    host: "localhost",
    port: 3000,
    debug: false,
    timeout: 5000
  };

  return { ...defaults, ...userConfig };
}

const config = createConfig({ port: 8080, debug: true });
console.log(config);
// { host: "localhost", port: 8080, debug: true, timeout: 5000 }

דסטרקצ'רינג בלולאה

const users = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 },
  { name: "Charlie", age: 35 }
];

for (const { name, age } of users) {
  console.log(`${name} is ${age}`);
}

// or with forEach
users.forEach(({ name, age }) => {
  console.log(`${name} is ${age}`);
});

סיכום

דסטרקצ'רינג

  • מערכים: const [a, b, c] = [1, 2, 3] - לפי סדר
  • אובייקטים: const { name, age } = person - לפי שם
  • שינוי שם: const { name: fullName } = person
  • ברירת מחדל: const { name = "unknown" } = person
  • מקונן: const { address: { city } } = person
  • בפרמטרים: function greet({ name, age }) {}

Rest

  • מערכים: const [first, ...rest] = arr
  • אובייקטים: const { name, ...rest } = obj
  • פרמטרים: function sum(...nums) {}

Spread

  • מערכים: [...arr1, ...arr2]
  • אובייקטים: { ...obj1, ...obj2 }
  • ארגומנטים: Math.max(...numbers)

העתקה

  • רדודה: { ...obj } או [...arr]
  • עמוקה: structuredClone(obj)