ویرگول
ورودثبت نام
Navid Barsalari
Navid Barsalariمهندس ارشد نرم‌افزار | تکنیکال لید | +۱۰ سال سابقه علاقه‌مند به System Design، توسعه بک‌اند (Go / Node.js) و معماری دیتابیس. تمرکز فعلی من روی ساخت و توسعه سرویس‌های مقیاس‌پذیر B2B است.
Navid Barsalari
Navid Barsalari
خواندن ۹ دقیقه·۱۶ روز پیش

Set و WeakSet در JavaScript: راهنمای جامع از مبتدی تا حرفه‌ای

مقدمه: چرا Array کافی نیست؟

فرض کنید می‌خواهید لیستی از کاربران آنلاین را نگه دارید. با Array می‌نویسید:

const onlineUsers = []; function addUser(userId) { if (!onlineUsers.includes(userId)) { onlineUsers.push(userId); } } addUser(101); addUser(102); addUser(101); // تکراری!

مشکل چیست؟

  • Performance: ()includes باید کل آرایه را بگردد → O(n)

  • تکرار: باید خودمان چک کنیم که آیتم تکراری نباشد

  • حذف: ()splice برای حذف یک آیتم خاص کند است

راه حل: Set - یک ساختار داده که فقط مقادیر یکتا نگه می‌دارد و عملیات‌هایش O(1) است.

بخش ۱: Set - مجموعه‌های یکتا

۱.۱ ایجاد و استفاده پایه

// ایجاد Set خالی const mySet = new Set(); // ایجاد با مقادیر اولیه const numbers = new Set([1, 2, 3, 3, 4]); // {1, 2, 3, 4} // اضافه کردن mySet.add(10); mySet.add(20); mySet.add(10); // تکراری، اضافه نمی‌شود console.log(mySet.size); // 2 // چک کردن وجود console.log(mySet.has(10)); // true // حذف mySet.delete(10); // پاک کردن همه mySet.clear();

۱.۲ تفاوت Set با Array

مقادیر تکراری:

  • Array: مجاز است، می‌توانید چندین بار یک مقدار را اضافه کنید

  • Set: غیرمجاز، هر مقدار فقط یک بار ذخیره می‌شود

دسترسی با Index:

  • Array: arr[0] برای دسترسی به اولین عنصر

  • Set: ندارد، باید با Iterator یا تبدیل به Array دسترسی پیدا کنید

جستجو (has یا includes):

  • Array: پیچیدگی )O(n) - باید کل آرایه را بگردد

  • Set: پیچیدگی O(1) - جستجوی فوری با Hash Table

حذف عنصر:

  • Array: پیچیدگی O(n) - باید پیدا کند و Index ها را جابجا کند

  • Set: پیچیدگی O(1) - حذف فوری

حفظ ترتیب:

  • هر دو ترتیب اضافه شدن (insertion order) را حفظ می‌کنند

بخش ۲: کاربردهای واقعی Set

۲.۱ حذف تکراری از آرایه

// روش قدیمی (کند) function removeDuplicates(arr) { return arr.filter((item, index) => arr.indexOf(item) === index); } // روش حرفه‌ای با Set function removeDuplicatesFast(arr) { return [...new Set(arr)]; } const numbers = [1, 2, 2, 3, 4, 4, 5]; console.log(removeDuplicatesFast(numbers)); // [1, 2, 3, 4, 5]

Performance:

Array method: O(n^2) Set method:O(n)

۲.۲ عملیات ریاضی روی مجموعه‌ها

// اجتماع (Union) function union(setA, setB) { return new Set([...setA, ...setB]); } // اشتراک (Intersection) function intersection(setA, setB) { return new Set([...setA].filter(x => setB.has(x))); } // تفاضل (Difference) function difference(setA, setB) { return new Set([...setA].filter(x => !setB.has(x))); } // مثال const admins = new Set(['user1', 'user2', 'user3']); const moderators = new Set(['user2', 'user3', 'user4']); console.log(union(admins, moderators)); // Set {'user1', 'user2', 'user3', 'user4'} console.log(intersection(admins, moderators)); // Set {'user2', 'user3'} console.log(difference(admins, moderators)); // Set {'user1'}

۲.۳ ردیابی کاربران آنلاین (Real-time)

class OnlineUsersTracker { constructor() { this.onlineUsers = new Set(); } userConnected(userId) { this.onlineUsers.add(userId); console.log(`User ${userId} is online. Total: ${this.onlineUsers.size}`); } userDisconnected(userId) { this.onlineUsers.delete(userId); console.log(`User ${userId} went offline. Total: ${this.onlineUsers.size}`); } isOnline(userId) { return this.onlineUsers.has(userId); // O(1) } getOnlineCount() { return this.onlineUsers.size; } } // استفاده در WebSocket const tracker = new OnlineUsersTracker(); tracker.userConnected(101); tracker.userConnected(102); tracker.userConnected(101); // تکراری، اثری ندارد console.log(tracker.isOnline(101)); // true

۲.۴ جلوگیری از Callback تکراری

class EventEmitter { constructor() { this.listeners = new Map(); // event -> Set of callbacks } on(event, callback) { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event).add(callback); } off(event, callback) { if (this.listeners.has(event)) { this.listeners.get(event).delete(callback); } } emit(event, data) { if (this.listeners.has(event)) { this.listeners.get(event).forEach(callback => callback(data)); } } } // استفاده const emitter = new EventEmitter(); const handler = (data) => console.log('Received:', data); emitter.on('message', handler); emitter.on('message', handler); // تکراری، فقط یک بار اضافه می‌شود emitter.emit('message', 'Hello'); // فقط یک بار اجرا می‌شود

بخش ۳: WeakSet - مدیریت حافظه هوشمند

۳.۱ تفاوت Set و WeakSet
نوع مقادیر:

  • Set: هر نوع داده‌ای (number, string, object, …)

  • WeakSet: فقط Object (نمی‌توان primitive اضافه کرد)

Garbage Collection:

  • Set: رفرنس قوی نگه می‌دارد، مانع آزادسازی حافظه می‌شود

  • WeakSet: رفرنس ضعیف، اجازه می‌دهد Garbage Collector حافظه را آزاد کند

قابلیت Iterate:

  • Set: بله، می‌توان با for...of یا forEach پیمایش کرد

  • WeakSet: خیر، غیرقابل پیمایش است

متدهای موجود:

  • Set: add, has, delete, clear, size

  • WeakSet: فقط add, has, delete (بدون clear و size)

کاربرد اصلی:

  • Set: ذخیره دائمی داده‌های یکتا

  • WeakSet: ذخیره موقت برای Metadata و Tracking


۳.۲ مشکل Memory Leak با Set

// ❌ مشکل: Memory Leak let user = { id: 1, name: 'Gholi' }; const processedUsers = new Set(); processedUsers.add(user); user = null; // می‌خواهیم user را آزاد کنیم // اما Set هنوز رفرنس دارد! // Garbage Collector نمی‌تواند حافظه را آزاد کند console.log(processedUsers.size); // 1 -------- // ✅ راه حل: WeakSet let user = { id: 1, name: 'Gholi' }; const processedUsers = new WeakSet(); processedUsers.add(user); user = null; // حالا Garbage Collector می‌تواند حافظه را آزاد کند // WeakSet به صورت خودکار آبجکت را حذف می‌کند


۳.۳ کاربرد واقعی: ردیابی DOM Elements

class DOMTracker { constructor() { this.trackedElements = new WeakSet(); } track(element) { if (!(element instanceof HTMLElement)) { throw new Error('Only DOM elements can be tracked'); } this.trackedElements.add(element); console.log('Element tracked'); } isTracked(element) { return this.trackedElements.has(element); } } // استفاده const tracker = new DOMTracker(); let button = document.createElement('button'); tracker.track(button); console.log(tracker.isTracked(button)); // true // وقتی button از DOM حذف شود button.remove(); button = null; // WeakSet به صورت خودکار حافظه را آزاد می‌کند // Memory Leak نداریم!

۳.۴ کاربرد: Private Data Pattern

const privateData = new WeakSet(); class SecureUser { constructor(name) { this.name = name; privateData.add(this); // علامت‌گذاری به عنوان معتبر } static isValid(user) { return privateData.has(user); } } // استفاده const user1 = new SecureUser('Gholi'); const fakeUser = { name: 'Hacker' }; console.log(SecureUser.isValid(user1)); // true console.log(SecureUser.isValid(fakeUser)); // false

۳.۵ کاربرد: جلوگیری از Circular Reference

function deepClone(obj, cloned = new WeakSet()) { // جلوگیری از Infinite Loop if (cloned.has(obj)) { throw new Error('Circular reference detected'); } if (typeof obj !== 'object' || obj === null) { return obj; } cloned.add(obj); const clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { clone[key] = deepClone(obj[key], cloned); } return clone; } // مثال با Circular Reference const user = { name: 'Gholi' }; user.self = user; // Circular! try { deepClone(user); } catch (e) { console.log(e.message); // "Circular reference detected" }

بخش ۴: نکات Performance و بهینه‌سازی

۴.۱ مقایسه سرعت

// Benchmark: جستجو در 1 میلیون آیتم const size = 1_000_000; // Array const arr = Array.from({ length: size }, (_, i) => i); console.time('Array includes'); arr.includes(999_999); console.timeEnd('Array includes'); // ~10ms // Set const set = new Set(arr); console.time('Set has'); set.has(999_999); console.timeEnd('Set has'); // ~0.001ms (1000x سریع‌تر!)

۴.۲ مصرف حافظه

// Array: 8 bytes per item (number) + overhead const arr = new Array(1_000_000).fill(1); // Set: ~24 bytes per item (hash table overhead) const set = new Set(arr); // نتیجه: Set حافظه بیشتری مصرف می‌کند // اما سرعت بالاتری دارد

قانون کلی:

  • اگر داده‌ها کمتر از ۱۰۰۰ آیتم: Array کافی است

  • اگر نیاز به جستجوی مکرر: Set

  • اگر نیاز به Index: Array

  • اگر نیاز به Garbage Collection: WeakSet

۴.۳ بهینه‌سازی: Bulk Operations

// ❌ کند: اضافه کردن تک تک const set = new Set(); for (let i = 0; i < 10000; i++) { set.add(i); } // ✅ سریع: ایجاد با آرایه const fastSet = new Set(Array.from({ length: 10000 }, (_, i) => i));

بخش ۵: نکات امنیتی

۵.۱ جلوگیری از Prototype Pollution

// ❌ خطرناک const userRoles = new Set(); userRoles.add('admin'); // حمله Object.prototype.isAdmin = true; console.log(userRoles.isAdmin); // true (خطرناک!) // ✅ امن function hasRole(set, role) { return set.has(role); // فقط از متدهای Set استفاده کن }

۵.۲ Sanitization ورودی

class SecureSet { constructor() { this.data = new Set(); } add(value) { // فقط مقادیر معتبر if (typeof value === 'string' && value.length < 100) { this.data.add(value); } else { throw new Error('Invalid input'); } } has(value) { return this.data.has(value); } }

۵.۳ Rate Limiting با Set

class RateLimiter { constructor(maxRequests, windowMs) { this.maxRequests = maxRequests; this.windowMs = windowMs; this.requests = new Map(); // IP -> Set of timestamps } isAllowed(ip) { const now = Date.now(); if (!this.requests.has(ip)) { this.requests.set(ip, new Set()); } const userRequests = this.requests.get(ip); // حذف timestamp های قدیمی for (let timestamp of userRequests) { if (now - timestamp > this.windowMs) { userRequests.delete(timestamp); } } if (userRequests.size >= this.maxRequests) { return false; // Rate limit exceeded } userRequests.add(now); return true; } } // استفاده const limiter = new RateLimiter(5, 60000); // 5 requests per minute console.log(limiter.isAllowed('192.168.1.1')); // true // ... 5 بار دیگر console.log(limiter.isAllowed('192.168.1.1')); // false (blocked)

بخش ۶: سوالات مصاحبه

سوال ۱: تفاوت Set و Array چیست؟

پاسخ:

  • Set فقط مقادیر یکتا نگه می‌دارد

  • عملیات has, add, delete در Set: O(1)O(1)O(1)، در Array: O(n)O(n)O(n)

  • Set Index ندارد، Array دارد

  • Set برای جستجوی سریع، Array برای ترتیب و دسترسی Index

سوال ۲: چه زمانی از WeakSet استفاده کنیم؟

پاسخ:

  • وقتی می‌خواهیم Object ها را Track کنیم بدون اینکه مانع Garbage Collection شویم

  • مثال: ردیابی DOM Elements، Private Data، جلوگیری از Circular Reference

سوال ۳: چرا WeakSet قابل Iterate نیست؟

پاسخ:

چون Garbage Collector می‌تواند هر لحظه آبجکت‌ها را حذف کند. اگر Iterate می‌شد، نتیجه غیرقابل پیش‌بینی بود.

سوال ۴: کد زیر چه خروجی دارد؟

const set = new Set([1, 2, 3]); set.add(1); set.add('1'); console.log(set.size);

پاسخ: 4

چون 1 (number) و '1' (string) دو مقدار متفاوت هستند (strict equality).

سوال ۵: چطور Set را به Array تبدیل کنیم؟

const set = new Set([1, 2, 3]); // روش 1: Spread const arr1 = [...set]; // روش 2: Array.from const arr2 = Array.from(set); // روش 3: Loop const arr3 = []; set.forEach(item => arr3.push(item));

سوال ۶: پیاده‌سازی Set با Object

class MySet { constructor() { this.items = {}; this.length = 0; } add(value) { if (!this.has(value)) { this.items[value] = true; this.length++; } return this; } has(value) { return this.items.hasOwnProperty(value); } delete(value) { if (this.has(value)) { delete this.items[value]; this.length--; return true; } return false; } clear() { this.items = {}; this.length = 0; } get size() { return this.length; } }

بخش ۷: Best Practices

✅ استفاده صحیح

// 1. برای یکتاسازی const uniqueIds = new Set(arrayWithDuplicates); // 2. برای جستجوی سریع const allowedUsers = new Set(['user1', 'user2', 'user3']); if (allowedUsers.has(currentUser)) { /* ... */ } // 3. برای عملیات مجموعه‌ای const admins = new Set([...]); const moderators = new Set([...]); const allStaff = new Set([...admins, ...moderators]); // 4. WeakSet برای DOM tracking const clickedElements = new WeakSet(); element.addEventListener('click', () => { clickedElements.add(element); });

❌ استفاده نادرست

// 1. استفاده از Set برای داده‌های کم const smallList = new Set([1, 2, 3]); // Array بهتر است // 2. نیاز به Index const set = new Set([1, 2, 3]); // set[0] ❌ کار نمی‌کند // 3. WeakSet با Primitive const weak = new WeakSet(); weak.add(123); // ❌ TypeError // 4. Iterate روی WeakSet const weak = new WeakSet(); for (let item of weak) { } // ❌ TypeError

جمع‌بندی

Set:

  • برای مقادیر یکتا

  • Performance بالا O(1)

  • قابل Iterate

  • مصرف حافظه بیشتر از Array

WeakSet:

  • فقط Object

  • Garbage Collection دوستانه

  • غیرقابل Iterate

  • برای Metadata و Tracking

چه زمانی استفاده کنیم؟
حذف تکراری از آرایه:

const unique = [...new Set(array)];

انتخاب: Set

جستجوی مکرر (بیش از ۱۰۰۰ بار):

if (mySet.has(value)) { }

انتخاب: Set

ترتیب مهم است + نیاز به Index:

const item = arr[5];

انتخاب: Array

Track کردن Objects بدون Memory Leak:

const processed = new WeakSet(); processed.add(obj);

انتخاب: WeakSet

DOM Element tracking:

const clicked = new WeakSet(); element.addEventListener('click', () => clicked.add(element));

انتخاب: WeakSet

داده‌های کمتر از ۱۰۰۰ آیتم:

const smallList = [1, 2, 3];

انتخاب: Array (سادگی مهم‌تر از Performance)

Rate Limiting:

const requests = new Map(); // IP -> Set of timestamps


نکته نهایی: Set و WeakSet ابزارهای قدرتمندی هستند، اما باید در جای مناسب استفاده شوند. همیشه Performance و Memory را با هم در نظر بگیرید.

garbage collectionjavascripttypescriptreact
۵
۰
Navid Barsalari
Navid Barsalari
مهندس ارشد نرم‌افزار | تکنیکال لید | +۱۰ سال سابقه علاقه‌مند به System Design، توسعه بک‌اند (Go / Node.js) و معماری دیتابیس. تمرکز فعلی من روی ساخت و توسعه سرویس‌های مقیاس‌پذیر B2B است.
شاید از این پست‌ها خوشتان بیاید