לדלג לתוכן

2.6 מערכים מתודות מתקדמות הרצאה

מתודות מתקדמות של מערכים - Advanced Array Methods

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

המתודות האלה חשובות במיוחד כי הן הבסיס לעבודה עם React בהמשך הקורס.


forEach - מעבר על מערך

המתודה forEach מריצה פונקציה על כל אלמנט במערך. היא דומה ללולאת for רגילה, אבל בצורה יותר נקייה.

const names = ["Alice", "Bob", "Charlie"];

// using forEach
names.forEach(function(name) {
  console.log("Hello " + name);
});

// same thing with arrow function
names.forEach((name) => {
  console.log("Hello " + name);
});

// the callback gets 3 arguments: element, index, array
names.forEach((name, index) => {
  console.log(index + ": " + name);
});
// 0: Alice
// 1: Bob
// 2: Charlie

בפייתון הייתם כותבים:

// Python equivalent:
// for name in names:
//     print(f"Hello {name}")

// Python with index:
// for i, name in enumerate(names):
//     print(f"{i}: {name}")

שימו לב - forEach לא מחזירה כלום (מחזירה undefined). היא רק מריצה קוד על כל אלמנט.


map - טרנספורמציה של מערך

map היא אחת המתודות הכי חשובות ב-JavaScript. היא מריצה פונקציה על כל אלמנט ומחזירה מערך חדש עם התוצאות.

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

const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// the original array is NOT changed
console.log(numbers); // [1, 2, 3, 4, 5]

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

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

const names = users.map((user) => user.name);
console.log(names); // ["Alice", "Bob", "Charlie"]

const descriptions = users.map((user) => `${user.name} is ${user.age} years old`);
console.log(descriptions);
// ["Alice is 25 years old", "Bob is 30 years old", "Charlie is 35 years old"]

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

// Python equivalent:
// doubled = [num * 2 for num in numbers]
// names = [user["name"] for user in users]

// or using map():
// doubled = list(map(lambda num: num * 2, numbers))

למה map כל כך חשובה? כי ב-React נשתמש בה כל הזמן כדי להפוך מערך של נתונים לרשימת אלמנטים בממשק המשתמש.


ההבדל בין forEach ל-map

זה הבדל קריטי שחייבים להבין:

const numbers = [1, 2, 3];

// forEach - returns undefined
const result1 = numbers.forEach((num) => num * 2);
console.log(result1); // undefined

// map - returns a new array
const result2 = numbers.map((num) => num * 2);
console.log(result2); // [2, 4, 6]
  • forEach - משמשת כשרוצים לבצע פעולה (side effect) כמו הדפסה, שמירה ל-DB וכו'. לא מחזירה כלום.
  • map - משמשת כשרוצים לטרנספורם מערך למערך חדש. תמיד מחזירה מערך חדש.

כלל אצבע: אם אתם צריכים את התוצאה - השתמשו ב-map. אם לא - השתמשו ב-forEach.


filter - סינון מערך

filter מחזירה מערך חדש שמכיל רק את האלמנטים שעברו את התנאי.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const evens = numbers.filter((num) => num % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

const bigNumbers = numbers.filter((num) => num > 5);
console.log(bigNumbers); // [6, 7, 8, 9, 10]

דוגמה עם אובייקטים:

const products = [
  { name: "Laptop", price: 5000, inStock: true },
  { name: "Phone", price: 3000, inStock: false },
  { name: "Tablet", price: 2000, inStock: true },
  { name: "Watch", price: 1000, inStock: true }
];

const available = products.filter((product) => product.inStock);
console.log(available);
// [{ name: "Laptop", ... }, { name: "Tablet", ... }, { name: "Watch", ... }]

const expensive = products.filter((product) => product.price > 2500);
console.log(expensive);
// [{ name: "Laptop", ... }, { name: "Phone", ... }]

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

// Python equivalent:
// evens = [num for num in numbers if num % 2 == 0]
// available = [p for p in products if p["in_stock"]]

// or using filter():
// evens = list(filter(lambda num: num % 2 == 0, numbers))

reduce - צמצום לערך יחיד

reduce היא המתודה הכי חזקה (וגם הכי מבלבלת בהתחלה). היא מצמצמת את כל המערך לערך יחיד.

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

// sum all numbers
const sum = numbers.reduce((accumulator, current) => {
  return accumulator + current;
}, 0);
console.log(sum); // 15

// shorter version
const sum2 = numbers.reduce((acc, num) => acc + num, 0);

איך זה עובד:
- הארגומנט השני של reduce (כאן: 0) הוא הערך ההתחלתי של ה-accumulator
- בכל איטרציה, הפונקציה מקבלת את ה-accumulator הנוכחי ואת האלמנט הנוכחי
- מה שמחזירים מהפונקציה הופך ל-accumulator בסיבוב הבא
- בסוף, ה-accumulator האחרון הוא התוצאה

בואו נעקוב אחרי זה צעד אחרי צעד:

// numbers = [1, 2, 3, 4, 5], initial value = 0

// step 1: acc=0,  current=1 -> return 0+1=1
// step 2: acc=1,  current=2 -> return 1+2=3
// step 3: acc=3,  current=3 -> return 3+3=6
// step 4: acc=6,  current=4 -> return 6+4=10
// step 5: acc=10, current=5 -> return 10+5=15
// result: 15

דוגמאות נוספות:

// find the maximum value
const max = numbers.reduce((acc, num) => num > acc ? num : acc, -Infinity);
console.log(max); // 5

// count occurrences
const fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const count = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
console.log(count); // { apple: 3, banana: 2, cherry: 1 }

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

// Python equivalent:
// from functools import reduce
// total = reduce(lambda acc, num: acc + num, numbers, 0)

// or just: total = sum(numbers)

find ו-findIndex - מציאת אלמנט

find מחזירה את האלמנט הראשון שעומד בתנאי. findIndex מחזירה את האינדקס שלו.

const users = [
  { id: 1, name: "Alice", role: "admin" },
  { id: 2, name: "Bob", role: "user" },
  { id: 3, name: "Charlie", role: "user" },
  { id: 4, name: "Diana", role: "admin" }
];

// find the first admin
const admin = users.find((user) => user.role === "admin");
console.log(admin); // { id: 1, name: "Alice", role: "admin" }

// find user by id
const bob = users.find((user) => user.id === 2);
console.log(bob); // { id: 2, name: "Bob", role: "user" }

// if nothing found - returns undefined
const notFound = users.find((user) => user.id === 999);
console.log(notFound); // undefined

// findIndex - returns the index
const adminIndex = users.findIndex((user) => user.role === "admin");
console.log(adminIndex); // 0

// if nothing found - returns -1
const notFoundIndex = users.findIndex((user) => user.id === 999);
console.log(notFoundIndex); // -1

ההבדל מ-filter: בעוד ש-filter מחזירה את כל האלמנטים שעומדים בתנאי (מערך), find מחזירה רק את הראשון (ערך בודד).


some ו-every - בדיקת תנאים

some בודקת אם לפחות אלמנט אחד עומד בתנאי. every בודקת אם כל האלמנטים עומדים בתנאי.

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

// some - is there at least one even number?
const hasEven = numbers.some((num) => num % 2 === 0);
console.log(hasEven); // true

// every - are all numbers positive?
const allPositive = numbers.every((num) => num > 0);
console.log(allPositive); // true

// every - are all numbers greater than 3?
const allBiggerThan3 = numbers.every((num) => num > 3);
console.log(allBiggerThan3); // false

דוגמה מעשית - ולידציה של טופס:

const formFields = [
  { name: "email", value: "test@test.com", valid: true },
  { name: "password", value: "1234", valid: false },
  { name: "name", value: "John", valid: true }
];

const isFormValid = formFields.every((field) => field.valid);
console.log(isFormValid); // false - password is not valid

const hasAnyValue = formFields.some((field) => field.value.length > 0);
console.log(hasAnyValue); // true

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

// Python equivalent:
// has_even = any(num % 2 == 0 for num in numbers)  -> some
// all_positive = all(num > 0 for num in numbers)    -> every

sort - מיון מערך

sort ממיינת את המערך. אבל יש בה מלכודת שחשוב להכיר.

מיון מחרוזות - עובד כצפוי

const fruits = ["banana", "apple", "cherry"];
fruits.sort();
console.log(fruits); // ["apple", "banana", "cherry"]

מיון מספרים - מלכודת!

const numbers = [10, 5, 40, 25, 1000, 1];
numbers.sort();
console.log(numbers); // [1, 10, 1000, 25, 40, 5] - wrong!

למה? כי sort בברירת מחדל ממיינת לפי סדר לקסיקוגרפי (כמו מילון) - היא ממירה הכל למחרוזות ומשווה תו-תו. "10" בא לפני "5" כי "1" בא לפני "5".

הפתרון - פונקציית השוואה - comparator

const numbers = [10, 5, 40, 25, 1000, 1];

// ascending order
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 5, 10, 25, 40, 1000]

// descending order
numbers.sort((a, b) => b - a);
console.log(numbers); // [1000, 40, 25, 10, 5, 1]

איך זה עובד?
- אם הפונקציה מחזירה מספר שלילי - a בא לפני b
- אם מחזירה מספר חיובי - b בא לפני a
- אם מחזירה 0 - הסדר לא משנה

מיון אובייקטים

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

// sort by age
users.sort((a, b) => a.age - b.age);
// [Alice(25), Charlie(30), Bob(35)]

// sort by name
users.sort((a, b) => a.name.localeCompare(b.name));
// [Alice, Bob, Charlie]

שימו לב ש-sort משנה את המערך המקורי (mutating). אם רוצים לשמור על המקור:

const sorted = [...numbers].sort((a, b) => a - b);
// or
const sorted2 = numbers.toSorted((a, b) => a - b); // modern - does not mutate

שרשור מתודות - Method Chaining

אחד הדברים הכי חזקים במתודות מערכים - אפשר לשרשר אותן. כל מתודה שמחזירה מערך מאפשרת לקרוא מתודה נוספת ישירות על התוצאה.

const users = [
  { name: "Alice", age: 17, score: 85 },
  { name: "Bob", age: 22, score: 92 },
  { name: "Charlie", age: 19, score: 78 },
  { name: "Diana", age: 25, score: 95 },
  { name: "Eve", age: 16, score: 88 }
];

// get names of adult users (18+), sorted by score (highest first)
const result = users
  .filter((user) => user.age >= 18)
  .sort((a, b) => b.score - a.score)
  .map((user) => user.name);

console.log(result); // ["Diana", "Bob", "Charlie"]

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


flat ו-flatMap

flat משטחת מערך מקונן:

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

const flat1 = nested.flat();
console.log(flat1); // [1, 2, 3, 4, [5, 6]] - one level

const flat2 = nested.flat(2);
console.log(flat2); // [1, 2, 3, 4, 5, 6] - two levels

const flatAll = nested.flat(Infinity);
console.log(flatAll); // [1, 2, 3, 4, 5, 6] - all levels

flatMap משלבת map ואחריה flat ברמה אחת:

const sentences = ["Hello World", "How are you"];

const words = sentences.flatMap((sentence) => sentence.split(" "));
console.log(words); // ["Hello", "World", "How", "are", "you"]

// without flatMap you'd get:
const wordsNested = sentences.map((sentence) => sentence.split(" "));
console.log(wordsNested); // [["Hello", "World"], ["How", "are", "you"]]

אי-שינוי - Immutability

עיקרון חשוב מאוד, במיוחד בהקשר של React: לא לשנות את המערך המקורי.

חלק מהמתודות משנות את המערך (mutating):
- sort(), reverse(), splice(), push(), pop(), shift(), unshift()

חלק לא משנות ומחזירות מערך חדש (non-mutating):
- map(), filter(), reduce(), concat(), slice(), flat(), flatMap()

גרסאות מודרניות שלא משנות:
- toSorted() - במקום sort()
- toReversed() - במקום reverse()
- toSpliced() - במקום splice()
- with(index, value) - במקום arr[index] = value

const original = [3, 1, 2];

// bad - mutates original
original.sort();
console.log(original); // [1, 2, 3] - original is changed!

// good - creates a new array
const original2 = [3, 1, 2];
const sorted = original2.toSorted();
console.log(sorted);    // [1, 2, 3]
console.log(original2); // [3, 1, 2] - original is untouched

למה זה חשוב? כי ב-React, כשמשנים state, צריך ליצור מערך חדש - אחרת React לא יודעת שמשהו השתנה ולא מרנדרת מחדש.


Object.groupBy - קיבוץ לפי קריטריון

פיצ'ר חדש יחסית שמאפשר לקבץ אלמנטים לפי קריטריון מסוים:

const people = [
  { name: "Alice", city: "Tel Aviv" },
  { name: "Bob", city: "Jerusalem" },
  { name: "Charlie", city: "Tel Aviv" },
  { name: "Diana", city: "Haifa" },
  { name: "Eve", city: "Jerusalem" }
];

const byCity = Object.groupBy(people, (person) => person.city);
console.log(byCity);
// {
//   "Tel Aviv": [{ name: "Alice", ... }, { name: "Charlie", ... }],
//   "Jerusalem": [{ name: "Bob", ... }, { name: "Eve", ... }],
//   "Haifa": [{ name: "Diana", ... }]
// }

לפני Object.groupBy, היינו צריכים לעשות את זה עם reduce:

// the old way with reduce
const byCity2 = people.reduce((groups, person) => {
  const city = person.city;
  if (!groups[city]) {
    groups[city] = [];
  }
  groups[city].push(person);
  return groups;
}, {});

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

// Python equivalent:
// from itertools import groupby
// or using defaultdict:
// from collections import defaultdict
// by_city = defaultdict(list)
// for person in people:
//     by_city[person["city"]].append(person)

דפוסים מעשיים - Practical Patterns

ספירה - Counting

const votes = ["yes", "no", "yes", "yes", "no", "abstain", "yes"];

const tally = votes.reduce((counts, vote) => {
  counts[vote] = (counts[vote] || 0) + 1;
  return counts;
}, {});

console.log(tally); // { yes: 4, no: 2, abstain: 1 }

שטוח וייחודי - Flatten and Unique

const tags = [
  ["js", "react"],
  ["js", "node"],
  ["python", "react"]
];

const uniqueTags = [...new Set(tags.flat())];
console.log(uniqueTags); // ["js", "react", "node", "python"]

סיכום עם תנאי

const orders = [
  { product: "Laptop", price: 5000, status: "completed" },
  { product: "Phone", price: 3000, status: "pending" },
  { product: "Tablet", price: 2000, status: "completed" },
  { product: "Watch", price: 1000, status: "cancelled" }
];

const completedTotal = orders
  .filter((order) => order.status === "completed")
  .reduce((total, order) => total + order.price, 0);

console.log(completedTotal); // 7000

טרנספורמציה של נתונים

const rawData = [
  { firstName: "alice", lastName: "smith", score: 85 },
  { firstName: "bob", lastName: "jones", score: 92 },
  { firstName: "charlie", lastName: "brown", score: 78 }
];

const processed = rawData
  .map((person) => ({
    fullName: person.firstName[0].toUpperCase() + person.firstName.slice(1) + " " +
              person.lastName[0].toUpperCase() + person.lastName.slice(1),
    grade: person.score >= 90 ? "A" : person.score >= 80 ? "B" : "C"
  }))
  .sort((a, b) => a.fullName.localeCompare(b.fullName));

console.log(processed);
// [
//   { fullName: "Alice Smith", grade: "B" },
//   { fullName: "Bob Jones", grade: "A" },
//   { fullName: "Charlie Brown", grade: "C" }
// ]

סיכום

  • forEach - מעבר על מערך (לא מחזירה כלום)
  • map - טרנספורמציה, מחזירה מערך חדש (חשוב מאוד ל-React)
  • filter - סינון, מחזירה מערך חדש
  • reduce - צמצום לערך יחיד
  • find / findIndex - מציאת אלמנט ראשון שעומד בתנאי
  • some / every - בדיקת תנאים (כמו any/all בפייתון)
  • sort - מיון (עם comparator למספרים)
  • flat / flatMap - שטיחת מערכים מקוננים
  • שרשור מתודות - הכוח האמיתי של המתודות האלה
  • אי-שינוי - תמיד העדיפו ליצור מערך חדש במקום לשנות את המקורי