وبلاگ
انواع دادهها و ساختارهای پیشرفته در جاوا اسکریپت
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
جاوا اسکریپت، زبانی که در ابتدا برای تعاملات ساده در مرورگر طراحی شده بود، امروزه به یکی از قدرتمندترین و پرکاربردترین زبانها در توسعه وب (فرانتاند و بکاند)، اپلیکیشنهای موبایل، دسکتاپ و حتی هوش مصنوعی تبدیل شده است. بخش عمدهای از این قدرت، مدیون انعطافپذیری و تکامل مستمر آن در زمینه مدیریت دادههاست. در حالی که بسیاری از توسعهدهندگان با انواع دادههای اولیه (مانند String، Number، Boolean، Null، Undefined) و ساختارهای دادهای رایج (مانند Object و Array) آشنا هستند، جاوا اسکریپت مجموعهای غنیتر و پیشرفتهتر از انواع دادهها و ساختارها را ارائه میدهد که برای حل مسائل پیچیدهتر، بهینهسازی عملکرد و مدیریت حافظه ضروری هستند.
در این مقاله جامع، ما به سفری عمیق در دنیای انواع دادهها و ساختارهای پیشرفته جاوا اسکریپت خواهیم رفت. هدف ما فراتر از یک معرفی ساده است؛ ما به بررسی جزئیات فنی، کاربردهای عملی، مزایا و معایب هر یک و چگونگی انتخاب بهترین ابزار برای هر موقعیت خاص خواهیم پرداخت. درک این مفاهیم نه تنها به شما کمک میکند کدهای کارآمدتر و قابل نگهداریتری بنویسید، بلکه دریچهای جدید به سوی الگوهای طراحی پیشرفته و راهحلهای خلاقانه برای چالشهای برنامهنویسی باز میکند. این محتوا برای توسعهدهندگانی طراحی شده است که حداقل آشنایی اولیه با جاوا اسکریپت دارند و اکنون به دنبال تعمیق دانش خود و ارتقای مهارتهایشان به سطح حرفهای هستند.
Symbol: فراتر از رشتهها و اعداد به عنوان کلید
در جاوا اسکریپت، تا قبل از ES6، کلیدهای ویژگیهای (properties) شیءها تنها میتوانستند رشته یا Symbol (قبل از ES6، تنها رشته) باشند. معرفی Symbol
در ES6 یک نوع داده اولیه جدید را به ارمغان آورد که به شما امکان میدهد کلیدهای ویژگی منحصربهفردی ایجاد کنید که با هیچ رشته یا Symbol دیگری تداخل نداشته باشند. این ویژگی برای سناریوهایی که نیاز به ایجاد ویژگیهای “خصوصی” یا “پنهان” در شیءها دارید، بدون خطر تصادم نام با کدهای خارجی یا کتابخانهها، بسیار ارزشمند است.
ویژگیهای کلیدی Symbol
- منحصربهفرد بودن: هر
Symbol
ایجاد شده، حتی اگر توضیحات یکسانی داشته باشند، کاملاً منحصربهفرد است. این خاصیت تضمین میکند که یک Symbol هرگز با یک رشته یا Symbol دیگر تداخل نخواهد داشت. - عدم قابلیت تکرارپذیری با روشهای معمول: Symbolها به طور پیشفرض توسط
for...in
لوپها یاObject.keys()
،Object.values()
، وObject.entries()
نادیده گرفته میشوند. برای دسترسی به آنها باید ازObject.getOwnPropertySymbols()
یاReflect.ownKeys()
استفاده کرد. این ویژگی به آنها خاصیت “پنهان” بودن میدهد. - عدم تبدیل ضمنی به رشته: یک Symbol را نمیتوان به طور ضمنی به یک رشته تبدیل کرد (مثلاً در الحاق رشتهها). برای نمایش آن به صورت رشته باید از
String(sym)
یاsym.toString()
استفاده کرد.
ایجاد Symbol
Symbolها با فراخوانی تابع Symbol()
ایجاد میشوند. میتوانید یک رشته توضیحی اختیاری به عنوان آرگومان به آن پاس دهید که صرفاً برای اشکالزدایی (debugging) مفید است و بر منحصربهفرد بودن Symbol تأثیری ندارد:
const myUniqueSymbol = Symbol('This is a unique identifier');
const anotherUniqueSymbol = Symbol('This is a unique identifier');
console.log(myUniqueSymbol === anotherUniqueSymbol); // false, هر دو منحصربهفرد هستند
const obj = {
[myUniqueSymbol]: 'Value for my unique symbol',
'regularKey': 'Regular string key value'
};
console.log(obj[myUniqueSymbol]); // 'Value for my unique symbol'
console.log(obj.regularKey); // 'Regular string key value'
// تلاش برای دسترسی به Symbol با کلید رشتهای ناموفق است
console.log(obj['myUniqueSymbol']); // undefined
استفاده از Symbol به عنوان ویژگی شیء
Symbolها به طور عمده برای ایجاد ویژگیهای شیء استفاده میشوند. این ویژگیها میتوانند مانند هر ویژگی دیگری به شیء اضافه شوند، اما ماهیت منحصربهفرد و غیرقابل تکرار آنها مزایای خاصی دارد:
const userID = Symbol('User Identifier');
const userRole = Symbol('User Role');
class User {
constructor(name, id, role) {
this.name = name;
this[userID] = id; // استفاده از Symbol به عنوان کلید
this[userRole] = role;
}
getRole() {
return this[userRole];
}
}
const admin = new User('Alice', 101, 'Administrator');
console.log(admin.name); // Alice
console.log(admin.getRole()); // Administrator
// Symbolها با for...in یا Object.keys() دیده نمیشوند
for (let key in admin) {
console.log(key); // فقط 'name' چاپ میشود
}
console.log(Object.keys(admin)); // ['name']
console.log(Object.getOwnPropertyNames(admin)); // ['name']
// برای دسترسی به Symbolها:
console.log(Object.getOwnPropertySymbols(admin)); // [Symbol(User Identifier), Symbol(User Role)]
console.log(admin[Object.getOwnPropertySymbols(admin)[0]]); // 101
Symbolهای سراسری (Global Symbols)
گاهی اوقات، نیاز به Symbolهایی دارید که در سراسر برنامه قابل اشتراکگذاری باشند و منحصربهفرد بودن آنها تضمین شود، اما نه به صورت یکتا در هر فراخوانی، بلکه یکتا در رجیستری سراسری Symbolها. برای این منظور، از Symbol.for(key)
و Symbol.keyFor(symbol)
استفاده میشود.
const globalSymbol1 = Symbol.for('app.config');
const globalSymbol2 = Symbol.for('app.config');
console.log(globalSymbol1 === globalSymbol2); // true (از رجیستری سراسری بازگردانده میشوند)
console.log(Symbol.keyFor(globalSymbol1)); // 'app.config'
const localSymbol = Symbol('app.config');
console.log(globalSymbol1 === localSymbol); // false (یکی سراسری، دیگری محلی)
console.log(Symbol.keyFor(localSymbol)); // undefined (فقط برای Symbolهای سراسری کار میکند)
Symbol.for()
ابتدا بررسی میکند که آیا Symbol با کلید داده شده در رجیستری سراسری وجود دارد یا خیر. اگر وجود داشت، همان Symbol را برمیگرداند؛ در غیر این صورت، یک Symbol جدید ایجاد کرده و آن را در رجیستری ذخیره میکند. این مکانیسم برای تعریف Symbolهایی که قرار است توسط بخشهای مختلف یک سیستم یا کتابخانه به اشتراک گذاشته شوند (مثلاً برای رویدادهای سفارشی یا پروتکلها) بسیار مفید است.
Symbolهای شناخته شده (Well-known Symbols)
جاوا اسکریپت مجموعهای از Symbolهای داخلی و از پیش تعریف شده را ارائه میدهد که به آنها “Symbolهای شناخته شده” میگویند. این Symbolها برای تغییر رفتار داخلی اشیاء در سناریوهای خاص استفاده میشوند و امکان متا برنامهنویسی (metaprogramming) را فراهم میکنند. برخی از مهمترین آنها عبارتند از:
Symbol.iterator
: تعیین میکند که یک شیء چگونه باید تکرارپذیر (iterable) باشد (مثلاً برایfor...of
).Symbol.toStringTag
: مقدار پیشفرضObject.prototype.toString()
را تغییر میدهد.Symbol.hasInstance
: نحوه کار عملگرinstanceof
را سفارشی میکند.Symbol.asyncIterator
: برای تکرارکنندههای ناهمزمان (async iterators) استفاده میشود.
مثال برای Symbol.iterator
:
class MyCollection {
constructor(...elements) {
this.elements = elements;
}
// این متد شیء را تکرارپذیر میکند
[Symbol.iterator]() {
let index = 0;
const elements = this.elements;
return {
next() {
if (index < elements.length) {
return { value: elements[index++], done: false };
} else {
return { done: true };
}
}
};
}
}
const collection = new MyCollection('a', 'b', 'c');
for (let item of collection) {
console.log(item); // a, b, c
}
Symbolها ابزاری قدرتمند برای افزودن رفتارها یا دادههای خاص به اشیاء بدون تداخل با ویژگیهای موجود هستند و نقش کلیدی در طراحی APIهای منعطف و مقیاسپذیر در جاوا اسکریپت ایفا میکنند.
BigInt: مدیریت اعداد صحیح بسیار بزرگ با دقت بینهایت
یکی از محدودیتهای اساسی نوع داده Number
در جاوا اسکریپت، عدم توانایی آن در نمایش دقیق اعداد صحیح بسیار بزرگ است. تمامی اعداد در جاوا اسکریپت به صورت اعداد ممیز شناور 64 بیتی با دقت مضاعف (double-precision floating-point numbers) طبق استاندارد IEEE 754 ذخیره میشوند. این استاندارد اجازه میدهد اعداد صحیح را به طور دقیق تنها تا 2^53 - 1
(یعنی Number.MAX_SAFE_INTEGER
) و -(2^53 - 1)
(یعنی Number.MIN_SAFE_INTEGER
) نمایش داد. هر عدد صحیحی خارج از این محدوده ممکن است دقت خود را از دست بدهد و به طور نادرست نمایش داده شود.
نیاز به BigInt
در بسیاری از سناریوهای مدرن توسعه وب، از جمله کار با APIهای بانکی، شناسههای منحصر به فرد بزرگ (مانند شناسههای پایگاه داده 64 بیتی)، رمزنگاری، یا محاسبات علمی، نیاز به اعدادی داریم که از محدوده ایمن Number
فراتر میروند. معرفی BigInt
در ES2020 این مشکل را حل کرد. BigInt
یک نوع داده اولیه جدید است که میتواند اعداد صحیح با دقت دلخواه (arbitrary-precision integers) را ذخیره و عملیات ریاضی روی آنها انجام دهد.
ایجاد BigInt
برای ایجاد یک BigInt
، میتوانید عدد صحیح را با پسوند n
مشخص کنید یا از تابع سازنده BigInt()
استفاده کنید:
const largeNumber = 123456789012345678901234567890n; // استفاده از پسوند 'n'
console.log(typeof largeNumber); // bigint
const anotherLargeNumber = BigInt("98765432109876543210"); // استفاده از تابع سازنده
console.log(anotherLargeNumber); // 98765432109876543210n
// تبدیل یک عدد Number به BigInt
const num = 123;
const bigNum = BigInt(num);
console.log(bigNum); // 123n
// نکته: نمیتوانید یک عدد ممیز شناور را به BigInt تبدیل کنید.
// BigInt(3.14); // TypeError: Cannot convert 3.14 to a BigInt
عملیات ریاضی با BigInt
تقریباً تمام عملگرهای ریاضیاتی که روی Number
کار میکنند، روی BigInt
نیز قابل استفاده هستند: جمع (+)، تفریق (-)، ضرب (*)، تقسیم (/)، باقیمانده (%), توان (**). تنها عملگر بیتی تکین (>>>
) که برای اعداد بدون علامت استفاده میشود، برای BigInt
پشتیبانی نمیشود.
const a = 100n;
const b = 20n;
console.log(a + b); // 120n
console.log(a * b); // 2000n
console.log(a / b); // 5n (تقسیم BigInt همیشه نتیجه صحیح برمیگرداند و قسمت اعشاری را حذف میکند)
console.log(a % b); // 0n
console.log(a ** 2n); // 10000n
ملاحظات مهم: عدم ترکیب با Number
مهمترین نکته در کار با BigInt
این است که نمیتوانید BigInt
را مستقیماً با Number
در عملیات ریاضی ترکیب کنید. این کار منجر به TypeError
میشود. برای انجام عملیات، باید هر دو عدد از یک نوع باشند:
const bigNum = 10n;
const regularNum = 5;
// console.log(bigNum + regularNum); // TypeError: Cannot mix BigInt and other types, use explicit conversions.
// تبدیل صریح برای انجام عملیات:
console.log(bigNum + BigInt(regularNum)); // 15n
console.log(Number(bigNum) + regularNum); // 15
این محدودیت به دلیل ماهیت متفاوت نمایش داخلی و جلوگیری از از دست دادن دقت است. تبدیل بین BigInt
و Number
باید به صورت صریح انجام شود و در هنگام تبدیل BigInt
به Number
باید مراقب از دست دادن احتمالی دقت برای اعداد بسیار بزرگ بود.
مقایسهها
مقایسه BigInt
با BigInt
یا BigInt
با Number
با استفاده از عملگرهای مقایسهای (==
, ===
, <
, >
, <=
, >=
) مجاز است. BigInt
و Number
زمانی که مقادیر عددی یکسانی دارند با ==
برابر در نظر گرفته میشوند، اما با ===
(برابر مطلق) خیر، زیرا انواع آنها متفاوت است.
console.log(10n == 10); // true
console.log(10n === 10); // false (انواع متفاوت)
console.log(10n > 5); // true
console.log(10n < 15n); // true
کاربردهای BigInt
BigInt
در سناریوهای زیر بسیار مفید است:
- شناسههای پایگاه داده: بسیاری از سیستمهای پایگاه داده از شناسههای 64 بیتی استفاده میکنند که از محدوده
Number.MAX_SAFE_INTEGER
فراتر میروند.BigInt
امکان مدیریت دقیق این شناسهها را در سمت کلاینت یا سرور (در Node.js) فراهم میکند. - رمزنگاری: عملیات رمزنگاری غالباً شامل اعداد بسیار بزرگ است که برای دقت بالا به
BigInt
نیاز دارند. - سیستمهای مالی: برای جلوگیری از خطاهای گرد کردن در محاسبات پولی که نیاز به دقت بسیار بالا دارند.
- سیستمهای اندازهگیری بسیار بزرگ: در محاسبات علمی یا فیزیکی که مقادیر ممکن است بسیار بزرگ یا بسیار کوچک باشند (اگرچه برای مقادیر بسیار کوچک نیاز به ممیز شناور با دقت بالا است).
با وجود BigInt
، جاوا اسکریپت اکنون ابزار قدرتمندی برای کار با اعداد صحیح با هر اندازه دلخواه در اختیار دارد و محدودیتهای دقت عددی گذشته را از بین برده است.
Map: کلید-مقدار با انعطافپذیری نامحدود
Map
یک ساختار داده است که مجموعهای از جفتهای کلید-مقدار را ذخیره میکند، شبیه به Object
. با این حال، تفاوتهای مهمی با Object
دارد که آن را در بسیاری از سناریوها به گزینهای قدرتمندتر تبدیل میکند. در Object
، کلیدها همیشه به رشته (String) یا Symbol تبدیل میشوند. اما در Map
، کلیدها میتوانند از هر نوع دادهای باشند: رشته، عدد، شیء، تابع، حتی null
یا undefined
. این انعطافپذیری Map را برای ذخیرهسازی دادههایی که کلیدهای آنها پیچیدهتر از رشتههای ساده هستند، ایدهآل میکند.
ویژگیهای کلیدی Map
- کلیدهای از هر نوع: قابلیت استفاده از هر مقدار (از جمله اشیاء و توابع) به عنوان کلید.
- حفظ ترتیب درج:
Map
ترتیب درج عناصر را به خاطر میسپارد و هنگام تکرار روی آن، عناصر به همان ترتیبی که اضافه شدهاند بازگردانده میشوند. (برخلاف اشیاء قدیمیتر که ترتیب کلیدها تضمین شده نبود، اگرچه در ES2015 به بعد، ترتیب کلیدهای عددی و سپس رشتهای در اشیاء تضمین شده است، اما برای کلیدهای Symbol همچنان متفاوت است). - اندازه (Size) قابل دسترسی:
Map
دارای یک ویژگی.size
است که به راحتی تعداد عناصر موجود در آن را برمیگرداند. (درObject
برای شمارش باید ازObject.keys().length
استفاده کرد). - عملکرد بهتر برای عملیات افزودن/حذف: در سناریوهای خاص، عملیات افزودن و حذف در
Map
میتواند عملکرد بهتری نسبت بهObject
داشته باشد. - قابلیت تکرارپذیری مستقیم:
Map
به طور پیشفرض تکرارپذیر (iterable) است، به این معنی که میتوانید به طور مستقیم ازfor...of
روی آن استفاده کنید تا جفتهای کلید-مقدار را پیمایش کنید.
ایجاد و استفاده از Map
Map
با فراخوانی تابع سازنده new Map()
ایجاد میشود. میتوانید یک آرایه از آرایههای دو عنصری ([key, value]
) برای مقداردهی اولیه به آن پاس دهید.
// ایجاد یک Map خالی
const myMap = new Map();
// اضافه کردن عناصر با متد .set()
myMap.set('name', 'Alice');
myMap.set(1, 'One');
myMap.set(true, 'Boolean Key');
const someObject = { id: 1 };
myMap.set(someObject, 'Value for an object key');
myMap.set(() => {}, 'Value for a function key');
console.log(myMap.size); // 5
// دریافت مقادیر با متد .get()
console.log(myMap.get('name')); // Alice
console.log(myMap.get(1)); // One
console.log(myMap.get(someObject)); // Value for an object key
console.log(myMap.get({ id: 1 })); // undefined (این یک شیء جدید است، نه همان شیء قبلی)
// بررسی وجود کلید با متد .has()
console.log(myMap.has('name')); // true
console.log(myMap.has('age')); // false
// حذف یک عنصر با متد .delete()
myMap.delete('name');
console.log(myMap.has('name')); // false
console.log(myMap.size); // 4
// پاک کردن تمام عناصر با متد .clear()
myMap.clear();
console.log(myMap.size); // 0
// ایجاد Map با مقداردهی اولیه از آرایه جفتها
const initialData = [['key1', 'value1'], ['key2', 'value2']];
const initializedMap = new Map(initialData);
console.log(initializedMap.get('key1')); // value1
پیمایش Map
Mapها تکرارپذیر هستند و میتوانند با for...of
یا forEach
پیمایش شوند:
const userRoles = new Map([
['Alice', 'Admin'],
['Bob', 'Editor'],
['Charlie', 'Viewer']
]);
// پیمایش با for...of برای دسترسی به جفتهای [key, value]
for (const [name, role] of userRoles) {
console.log(`${name} is a ${role}`);
}
// خروجی:
// Alice is a Admin
// Bob is a Editor
// Charlie is a Viewer
// پیمایش با forEach
userRoles.forEach((role, name) => {
console.log(`${name} has role: ${role}`);
});
// دریافت کلیدها، مقادیر یا جفتهای ورودی به صورت جداگانه
console.log(userRoles.keys()); // MapIterator { 'Alice', 'Bob', 'Charlie' }
console.log(userRoles.values()); // MapIterator { 'Admin', 'Editor', 'Viewer' }
console.log(userRoles.entries());// MapIterator { ['Alice', 'Admin'], ['Bob', 'Editor'], ['Charlie', 'Viewer'] }
// تبدیل Map به آرایه
const rolesArray = [...userRoles];
console.log(rolesArray); // [['Alice', 'Admin'], ['Bob', 'Editor'], ['Charlie', 'Viewer']]
کاربردهای Map
Map
در سناریوهای زیر بسیار مفید است:
- ذخیرهسازی دادههای مرتبط با اشیاء DOM: زمانی که میخواهید دادههای اضافی را به عناصر DOM پیوند دهید بدون اینکه آنها را مستقیماً به عنوان ویژگی به عناصر اضافه کنید (که میتواند منجر به نشت حافظه یا تداخل شود).
- پایگاه دادههای موقت یا کش (Cache): برای ذخیرهسازی نتایج عملیات گرانقیمت یا دادههایی که به سرعت نیاز به بازیابی دارند، با استفاده از کلیدهای پیچیده.
- پیادهسازی memoization: ذخیره نتایج توابع با ورودیهای خاص برای جلوگیری از محاسبات تکراری.
- گرافها و درختها: برای نمایش ساختارهایی که نیاز به ارجاعات پیچیده با استفاده از اشیاء به عنوان کلید دارند.
- جایگزینی برای اشیاء در مواردی که کلیدها غیر رشتهای هستند: هرگاه نیاز به استفاده از اعداد، اشیاء، یا حتی توابع به عنوان کلید دارید،
Map
گزینه برتر است.
با توجه به انعطافپذیری و عملکرد Map
، این ساختار داده اغلب جایگزین مناسبی برای Object
در مواقعی است که نیاز به کنترل بیشتر بر کلیدها و ترتیب عناصر دارید.
Set: مجموعههایی از مقادیر منحصربهفرد
Set
یک ساختار داده است که مجموعهای از مقادیر منحصربهفرد را ذخیره میکند. برخلاف Array
که میتواند شامل عناصر تکراری باشد، هر عنصر در یک Set
فقط یک بار میتواند وجود داشته باشد. این ویژگی Set
را برای سناریوهایی که نیاز به اطمینان از منحصربهفرد بودن عناصر دارید، ایدهآل میکند. Set
همچنین مانند Map
، ترتیب درج عناصر را حفظ میکند.
ویژگیهای کلیدی Set
- منحصربهفرد بودن عناصر:
Set
به طور خودکار مقادیر تکراری را حذف میکند. هنگامی که سعی میکنید یک مقدار موجود را اضافه کنید، هیچ اتفاقی نمیافتد وSet
بدون تغییر باقی میماند. - قابلیت نگهداری هر نوع داده: همانند
Map
، هر نوع دادهای میتواند به عنوان عنصر درSet
ذخیره شود، از جمله اشیاء، توابع،null
،undefined
،NaN
(NaN یکتاست، یعنی یک بار در Set میتواند باشد). - حفظ ترتیب درج: عناصر در همان ترتیبی که به
Set
اضافه شدهاند، پیمایش میشوند. - اندازه (Size) قابل دسترسی: ویژگی
.size
تعداد عناصر درSet
را برمیگرداند. - قابلیت تکرارپذیری مستقیم:
Set
تکرارپذیر است و میتوان آن را باfor...of
یاforEach
پیمایش کرد.
ایجاد و استفاده از Set
Set
با فراخوانی تابع سازنده new Set()
ایجاد میشود. میتوانید یک آرایه یا هر شیء تکرارپذیر دیگری را برای مقداردهی اولیه به آن پاس دهید.
// ایجاد یک Set خالی
const mySet = new Set();
// اضافه کردن عناصر با متد .add()
mySet.add(1);
mySet.add(5);
mySet.add('text');
mySet.add(1); // تلاش برای افزودن مقدار تکراری، نادیده گرفته میشود
const obj1 = { id: 1 };
const obj2 = { id: 2 };
mySet.add(obj1);
mySet.add(obj2);
mySet.add(obj1); // شیء obj1 مجدداً اضافه نمیشود
console.log(mySet.size); // 5 (1, 5, 'text', obj1, obj2)
// بررسی وجود عنصر با متد .has()
console.log(mySet.has(1)); // true
console.log(mySet.has(10)); // false
console.log(mySet.has(obj1)); // true
console.log(mySet.has({ id: 1 })); // false (این یک شیء جدید است)
// حذف یک عنصر با متد .delete()
mySet.delete(5);
console.log(mySet.size); // 4
console.log(mySet.has(5)); // false
// پاک کردن تمام عناصر با متد .clear()
mySet.clear();
console.log(mySet.size); // 0
// ایجاد Set با مقداردهی اولیه از آرایه
const numbers = [1, 2, 3, 2, 1, 4, 5, 4];
const uniqueNumbers = new Set(numbers);
console.log(uniqueNumbers); // Set { 1, 2, 3, 4, 5 }
پیمایش Set
Setها میتوانند با for...of
یا forEach
پیمایش شوند:
const fruits = new Set(['Apple', 'Banana', 'Orange']);
// پیمایش با for...of
for (const fruit of fruits) {
console.log(fruit);
}
// خروجی:
// Apple
// Banana
// Orange
// پیمایش با forEach
fruits.forEach(fruit => {
console.log(`I love ${fruit}`);
});
// دریافت iterator برای مقادیر (کلیدها و ورودیها در Set یکی هستند)
console.log(fruits.values()); // SetIterator { 'Apple', 'Banana', 'Orange' }
console.log(fruits.keys()); // SetIterator { 'Apple', 'Banana', 'Orange' }
console.log(fruits.entries());// SetIterator { ['Apple', 'Apple'], ['Banana', 'Banana'], ['Orange', 'Orange'] }
// تبدیل Set به آرایه
const fruitsArray = [...fruits];
console.log(fruitsArray); // ['Apple', 'Banana', 'Orange']
عملیات مجموعهای (Set Operations)
هرچند Set
متدهای داخلی برای عملیات مجموعهای مانند اجتماع (union)، اشتراک (intersection) و تفاضل (difference) ندارد، اما میتوان این عملیات را به راحتی با ترکیب متدهای Set
و Array
انجام داد:
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// اجتماع (Union): تمام عناصر موجود در حداقل یکی از مجموعهها
const union = new Set([...setA, ...setB]);
console.log(union); // Set { 1, 2, 3, 4, 5, 6 }
// اشتراک (Intersection): تمام عناصر موجود در هر دو مجموعه
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log(intersection); // Set { 3, 4 }
// تفاضل (Difference): تمام عناصر موجود در setA که در setB نیستند
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log(difference); // Set { 1, 2 }
کاربردهای Set
Set
در سناریوهای زیر بسیار مفید است:
- حذف عناصر تکراری از یک آرایه: سادهترین و کارآمدترین راه برای تبدیل یک آرایه با عناصر تکراری به آرایهای با عناصر منحصربهفرد.
- ردیابی موارد منحصربهفرد: مثلاً در یک سیستم ثبت بازدیدها، برای ردیابی IPهای منحصربهفرد بازدیدکنندگان.
- بررسی سریع وجود یک عنصر: عملیات
.has()
درSet
به طور متوسط پیچیدگی زمانی O(1) دارد که برای مجموعه دادههای بزرگ بسیار کارآمدتر از جستجو در یک آرایه است (O(n)). - مدیریت تگها یا برچسبها: برای اطمینان از اینکه هر تگ فقط یک بار به یک آیتم اختصاص داده شده است.
Set
ابزاری قدرتمند برای مدیریت مجموعههایی از دادههای منحصربهفرد است و در ترکیب با Array
و Map
، امکان ایجاد ساختارهای دادهای پیچیدهتر و بهینهتر را فراهم میکند.
WeakMap و WeakSet: بهینهسازی حافظه با ارجاعات ضعیف
WeakMap
و WeakSet
نسخههای خاصی از Map
و Set
هستند که با هدف بهینهسازی مصرف حافظه و جلوگیری از نشت حافظه (memory leaks) طراحی شدهاند. تفاوت اصلی آنها در نحوه نگهداری ارجاعات به عناصرشان است: WeakMap
و WeakSet
از "ارجاعات ضعیف" (weak references) استفاده میکنند، در حالی که Map
و Set
از "ارجاعات قوی" (strong references) استفاده میکنند.
ارجاعات قوی در مقابل ارجاعات ضعیف
- ارجاع قوی (Strong Reference): تا زمانی که یک شیء توسط حداقل یک ارجاع قوی نگهداری میشود، JavaScript Garbage Collector (GC) آن شیء را از حافظه حذف نمیکند. اگر یک شیء به عنوان کلید در
Map
یا عنصر درSet
استفاده شود،Map
/Set
یک ارجاع قوی به آن شیء نگه میدارد، حتی اگر هیچ ارجاع دیگری به آن شیء در برنامه وجود نداشته باشد. این میتواند منجر به نشت حافظه شود. - ارجاع ضعیف (Weak Reference): یک ارجاع ضعیف، مانع از حذف شیء توسط GC نمیشود. اگر تنها ارجاع به یک شیء، یک ارجاع ضعیف (مثلاً از
WeakMap
یاWeakSet
) باشد، GC میتواند آن شیء را از حافظه آزاد کند. هنگامی که یک شیء از حافظه حذف میشود، تمام ارجاعات ضعیف به آن شیء نیز به طور خودکار حذف میشوند.
WeakMap
WeakMap
شبیه به Map
است با این تفاوتهای کلیدی:
- کلیدها فقط میتوانند شیء باشند: کلیدها در
WeakMap
باید حتماً شیء باشند (null
،undefined
،Boolean
،Number
،String
،Symbol
به عنوان کلید مجاز نیستند). - ارجاعات ضعیف به کلیدها:
WeakMap
ارجاع ضعیفی به کلیدهای خود نگه میدارد. اگر تنها ارجاع به یک شیء، کلید آن درWeakMap
باشد، آن شیء و جفت کلید-مقدار مربوطه ازWeakMap
حذف خواهند شد زمانی که GC آن شیء را جمعآوری کند. - عدم قابلیت پیمایش:
WeakMap
قابلیت پیمایش (iteration) ندارد (یعنی نمیتوانید ازfor...of
،forEach
،.keys()
،.values()
،.entries()
استفاده کنید). دلیل این محدودیت این است که نمیتوان تضمین کرد چه زمانی یک کلید ممکن است توسط GC حذف شود، بنابراین حفظ ثبات در حین پیمایش غیرممکن است. - عدم دسترسی به
.size
: به دلیل عدم قابلیت پیمایش و ماهیت پویا،WeakMap
ویژگی.size
را ندارد.
متدهای WeakMap
WeakMap
متدهای .set(key, value)
، .get(key)
، .has(key)
و .delete(key)
را ارائه میدهد. متد .clear()
ندارد.
let obj1 = { name: 'Alice' };
let obj2 = { name: 'Bob' };
const myWeakMap = new WeakMap();
myWeakMap.set(obj1, 'Data for Alice');
myWeakMap.set(obj2, 'Data for Bob');
console.log(myWeakMap.get(obj1)); // Data for Alice
console.log(myWeakMap.has(obj2)); // true
obj1 = null; // ارجاع قوی به obj1 را حذف میکنیم
// در این مرحله، obj1 (و جفت آن در WeakMap) ممکن است توسط GC جمعآوری شود.
// نمیتوانیم بلافاصله آن را تأیید کنیم زیرا GC به صورت غیرقطعی عمل میکند.
// اما تضمین شده است که در نهایت حذف خواهد شد.
کاربردهای WeakMap
- دادههای خصوصی یا Metadata برای اشیاء: زمانی که میخواهید دادههای خصوصی یا اطلاعات اضافی را به یک شیء اضافه کنید، اما این دادهها باید همراه با شیء اصلی از بین بروند. به عنوان مثال، اطلاعات پیکربندی برای یک شیء DOM.
- کشینگ (Caching) با قابلیت GC: ایجاد یک کش که در آن آیتمهای کش شده به محض اینکه ارجاع دیگری به آنها نباشد، از حافظه حذف میشوند.
WeakSet
WeakSet
شبیه به Set
است با این تفاوتهای کلیدی:
- عناصر فقط میتوانند شیء باشند: عناصر در
WeakSet
باید حتماً شیء باشند (همان محدودیتهای کلید در WeakMap). - ارجاعات ضعیف به عناصر:
WeakSet
ارجاع ضعیفی به عناصر خود نگه میدارد. اگر تنها ارجاع به یک شیء، وجود آن درWeakSet
باشد، آن شیء ازWeakSet
حذف خواهد شد زمانی که GC آن را جمعآوری کند. - عدم قابلیت پیمایش: همانند
WeakMap
،WeakSet
نیز قابلیت پیمایش ندارد. - عدم دسترسی به
.size
:WeakSet
نیز ویژگی.size
را ندارد.
متدهای WeakSet
WeakSet
متدهای .add(value)
، .has(value)
و .delete(value)
را ارائه میدهد. متد .clear()
ندارد.
let elem1 = { id: 1 };
let elem2 = { id: 2 };
const myWeakSet = new WeakSet();
myWeakSet.add(elem1);
myWeakSet.add(elem2);
console.log(myWeakSet.has(elem1)); // true
elem1 = null; // ارجاع قوی به elem1 را حذف میکنیم
// elem1 (و وجود آن در WeakSet) ممکن است توسط GC جمعآوری شود.
کاربردهای WeakSet
- ردیابی اشیاء بدون جلوگیری از GC: زمانی که میخواهید ردیابی کنید که آیا یک شیء خاص قبلاً دیده شده است یا خیر، بدون اینکه مانع از جمعآوری آن توسط GC شوید. به عنوان مثال، برای جلوگیری از اضافه شدن یک شیء به یک پردازش خاص در صورت وجود قبلی آن.
- ثبت شنوندگان رویداد برای اشیاء DOM: میتوان از
WeakSet
برای نگهداری ارجاعات به عناصر DOM که شنونده رویداد به آنها اضافه شده است، استفاده کرد. هنگامی که یک عنصر DOM از صفحه حذف شود و هیچ ارجاع دیگری به آن نباشد، GC میتواند آن را همراه با ارجاع آن درWeakSet
حذف کند.
WeakMap
و WeakSet
ابزارهای پیشرفتهای هستند که در موارد خاصی که مدیریت حافظه و جلوگیری از نشت آن اهمیت حیاتی دارد، به کار میروند. درک تفاوت آنها با Map
و Set
و زمان استفاده از هر کدام، نشاندهنده تسلط بر جنبههای عمیقتر جاوا اسکریپت است.
Typed Arrays و ArrayBuffer: کارایی بالا با دادههای باینری
در حالی که آرایههای معمولی جاوا اسکریپت (Array
) بسیار انعطافپذیر هستند و میتوانند انواع دادههای مختلف را در خود نگه دارند، اما برای کار با دادههای عددی با حجم بالا و بهینهسازی عملکرد، به خصوص در عملیات سطح پایینتر مانند دستکاری پیکسلها در Canvas، پردازش صدا، WebSockets یا تعامل با WebAssembly، همیشه ایدهآل نیستند. اینجا جایی است که Typed Arrays و ArrayBuffer به میدان میآیند.
ArrayBuffer: بافر حافظه خام
ArrayBuffer
یک شیء است که یک قطعه حافظه دودویی (binary data buffer) با اندازه ثابت را نمایش میدهد. این به خودی خود هیچ گونه فرمت خاصی ندارد و نمیتوانید مستقیماً با آن کار کنید یا محتویات آن را دستکاری کنید. ArrayBuffer
فقط یک "نقطه" در حافظه است که میتوانید آن را به عنوان یک آرایه از بایتها در نظر بگیرید.
// ایجاد یک ArrayBuffer با ظرفیت 16 بایت
const buffer = new ArrayBuffer(16);
console.log(buffer.byteLength); // 16
برای خواندن یا نوشتن دادهها در یک ArrayBuffer
، به یک "نما" (view) نیاز دارید.
Typed Arrays: نماهایی با نوع مشخص
Typed Arrays نماهایی (views) هستند که به شما امکان میدهند به دادههای باینری ذخیره شده در یک ArrayBuffer
با نوع خاص و قالب مشخصی دسترسی پیدا کنید. هر Typed Array برای یک نوع داده عددی خاص طراحی شده است (مانند اعداد صحیح 8 بیتی، 16 بیتی، 32 بیتی یا اعداد ممیز شناور). این نماها عملکرد بهتری برای عملیات ریاضیاتی روی دادههای عددی فراهم میکنند، زیرا موتور جاوا اسکریپت دقیقاً میداند هر بایت چه نوع دادهای را نشان میدهد.
انواع متداول Typed Arrays عبارتند از:
Int8Array
: اعداد صحیح 8 بیتی علامتدار (-128 تا 127)Uint8Array
: اعداد صحیح 8 بیتی بدون علامت (0 تا 255)Int16Array
: اعداد صحیح 16 بیتی علامتدارUint16Array
: اعداد صحیح 16 بیتی بدون علامتInt32Array
: اعداد صحیح 32 بیتی علامتدارUint32Array
: اعداد صحیح 32 بیتی بدون علامتFloat32Array
: اعداد ممیز شناور 32 بیتیFloat64Array
: اعداد ممیز شناور 64 بیتی (دقت مضاعف)BigInt64Array
: اعداد صحیح 64 بیتی علامتدار (از نوع BigInt)BigUint64Array
: اعداد صحیح 64 بیتی بدون علامت (از نوع BigInt)
مثال استفاده از Typed Array
// ایجاد یک ArrayBuffer
const buffer = new ArrayBuffer(16); // 16 بایت حافظه خام
// ایجاد یک Uint32Array (اعداد صحیح 32 بیتی بدون علامت) به عنوان نما روی بافر
// هر عدد 32 بیتی 4 بایت فضا اشغال میکند. 16 بایت / 4 بایت = 4 عنصر.
const uint32Array = new Uint32Array(buffer);
console.log(uint32Array.length); // 4 (تعداد عناصر قابل ذخیره در این نما)
console.log(uint32Array.byteLength); // 16 (اندازه کلی نما در بایت)
console.log(uint32Array.byteOffset); // 0 (محل شروع نما از ابتدای بافر)
// اختصاص دادن مقادیر
uint32Array[0] = 42;
uint32Array[1] = 100;
uint32Array[2] = 200;
uint32Array[3] = 300;
console.log(uint32Array); // Uint32Array [ 42, 100, 200, 300 ]
// ایجاد یک نما دیگر با نوع متفاوت روی همان بافر
// هر عدد 8 بیتی 1 بایت فضا اشغال میکند. 16 بایت / 1 بایت = 16 عنصر.
const uint8Array = new Uint8Array(buffer);
console.log(uint8Array.length); // 16
// ببینید چگونه تغییرات در یک نما، در دیگری نیز منعکس میشود
console.log(uint8Array[0]); // 42 (بایت اول از 42)
console.log(uint8Array[1]); // 0
console.log(uint8Array[2]); // 0
console.log(uint8Array[3]); // 0 (اگر 42 به عنوان یک عدد 32 بیتی در نظر گرفته شود، تنها بایت اول آن 42 است و بقیهی بایتها 0 خواهند بود.)
uint8Array[0] = 255; // تغییر بایت اول
console.log(uint32Array[0]); // 255 (مقدار 42 در uint32Array به 255 تغییر کرد!)
// این به دلیل Endianness است (نحوه ذخیره بایتها برای یک عدد چند بایتی).
// در اکثر سیستمها، Little-endian رایج است، یعنی بایت کمارزشتر در آدرس حافظه پایینتر قرار میگیرد.
// 42 در 32-bit: 0x0000002A
// uint8Array[0] = 0x2A, uint8Array[1] = 0x00, uint8Array[2] = 0x00, uint8Array[3] = 0x00
// وقتی uint8Array[0] را به 255 (0xFF) تغییر میدهیم:
// 0xFF000000 (در Little-endian) که برابر با 255 است.
DataView: کنترل دقیقتر بر بایتها
DataView
یک نما (view) دیگر برای ArrayBuffer
است که برخلاف Typed Arrays، هیچ نوع دادهای به آن متصل نیست و به شما امکان میدهد بایتها را در هر آفستی (offset) با هر نوع دادهای بخوانید یا بنویسید و حتی Endianness را مشخص کنید (Big-endian یا Little-endian).
const buffer = new ArrayBuffer(8); // 8 بایت
const view = new DataView(buffer);
// نوشتن یک عدد صحیح 32 بیتی در آفست 0 (با Little-endian)
view.setInt32(0, 12345678, true); // true برای Little-endian
// خواندن یک عدد صحیح 16 بیتی از آفست 2 (با Little-endian)
console.log(view.getInt16(2, true)); // خروجی: 189 (بایتهای 2 و 3 از 12345678)
DataView
زمانی مفید است که شما نیاز دارید دادههای باینری را از یک منبع خارجی با فرمتهای پیچیده (مانند پروتکلهای شبکه) تجزیه کنید.
کاربردهای Typed Arrays و ArrayBuffer
- WebGL و Canvas 2D: برای کارایی بالا در دستکاری پیکسلها، دادههای هندسی و بافتها.
- پردازش صدا و ویدئو: برای کار با دادههای صوتی و تصویری خام.
- WebSockets: ارسال و دریافت دادههای باینری بهینه.
- WebAssembly: تعامل با کد WebAssembly که با حافظه خام کار میکند.
- فایلهای باینری: خواندن و نوشتن فایلها با فرمتهای خاص (مانند تصاویر).
- محاسبات عددی سنگین: زمانی که نیاز به انجام عملیات ریاضیاتی روی مجموعههای بزرگ اعداد با عملکرد بالا دارید.
با استفاده از Typed Arrays و ArrayBuffer، توسعهدهندگان جاوا اسکریپت میتوانند به صورت کارآمدتری با دادههای باینری سطح پایین کار کنند و به عملکردی نزدیک به زبانهای سطح پایینتر دست یابند، که این امکان را برای کاربردهای پیچیدهتر و پربارتر در مرورگر و Node.js فراهم میآورد.
Proxy و Reflect: متا برنامهنویسی و کنترل رفتار شیء
Proxy
و Reflect
دو ویژگی قدرتمند در ES6 هستند که امکان متا برنامهنویسی (metaprogramming) در جاوا اسکریپت را فراهم میکنند. متا برنامهنویسی به معنی نوشتن کدی است که کد دیگر را دستکاری یا تجزیه و تحلیل میکند. در جاوا اسکریپت، این به معنای توانایی "رهگیری" و "سفارشیسازی" عملیات اساسی روی اشیاء مانند دسترسی به ویژگیها، انتساب مقادیر، فراخوانی توابع و غیره است.
Proxy: رهگیر عملیات شیء
یک شیء Proxy
به شما امکان میدهد رفتار یک شیء دیگر (که به آن target
میگویند) را در عملیات اساسی (مانند دسترسی به ویژگیها، انتساب، فراخوانی) رهگیری و سفارشیسازی کنید. شما یک Proxy
را با دو آرگومان ایجاد میکنید: target
(شیء که قرار است پروکسی شود) و handler
(یک شیء شامل متدهایی به نام "traps" که عملیات رهگیری شده را تعریف میکنند).
// شیء هدف
const target = {
message1: "Hello",
message2: "World"
};
// شیء handler با traps
const handler = {
// trap برای عملیات 'get' (دسترسی به ویژگی)
get(target, property, receiver) {
console.log(`Getting property "${String(property)}"`);
// بازگرداندن مقدار اصلی ویژگی
return Reflect.get(target, property, receiver);
},
// trap برای عملیات 'set' (انتساب ویژگی)
set(target, property, value, receiver) {
if (property === 'message1' && typeof value !== 'string') {
throw new TypeError('message1 must be a string!');
}
console.log(`Setting property "${String(property)}" to "${value}"`);
// انتساب مقدار اصلی ویژگی
return Reflect.set(target, property, value, receiver);
}
};
// ایجاد یک شیء Proxy
const proxy = new Proxy(target, handler);
console.log(proxy.message1); // Getting property "message1"
// Hello
proxy.message2 = "Proxy World"; // Setting property "message2" to "Proxy World"
console.log(proxy.message2); // Getting property "message2"
// Proxy World
// proxy.message1 = 123; // TypeError: message1 must be a string!
در این مثال، هر بار که به یک ویژگی proxy
دسترسی پیدا میکنید (get
) یا مقداری به آن اختصاص میدهید (set
)، توابع get
و set
در handler
فراخوانی میشوند و به شما امکان میدهند قبل، در حین یا بعد از عملیات اصلی، منطق سفارشی را اجرا کنید. این مکانیسم بسیار قدرتمند است و بسیاری از فریمورکهای مدرن (مانند Vue 3.x برای Reactive System) از آن استفاده میکنند.
تعدادی از traps مهم Proxy
get(target, property, receiver)
: رهگیری دسترسی به ویژگیها.set(target, property, value, receiver)
: رهگیری انتساب ویژگیها.has(target, property)
: رهگیری عملگرin
.deleteProperty(target, property)
: رهگیری عملگرdelete
.apply(target, thisArg, argumentsList)
: رهگیری فراخوانی یک تابع پروکسی شده.construct(target, argumentsList, newTarget)
: رهگیری فراخوانی یک سازنده پروکسی شده باnew
.defineProperty(target, property, descriptor)
: رهگیریObject.defineProperty()
.getOwnPropertyDescriptor(target, property)
: رهگیریObject.getOwnPropertyDescriptor()
.getPrototypeOf(target)
: رهگیریObject.getPrototypeOf()
.setPrototypeOf(target, prototype)
: رهگیریObject.setPrototypeOf()
.isExtensible(target)
: رهگیریObject.isExtensible()
.preventExtensions(target)
: رهگیریObject.preventExtensions()
.ownKeys(target)
: رهگیریObject.keys()
،Object.getOwnPropertyNames()
،Object.getOwnPropertySymbols()
.
Reflect: یک API سازگار برای عملیات شیء
Reflect
یک شیء داخلی جدید در ES6 است که متدهایی را برای فراخوانی عملیات شیء فراهم میکند. این متدها تقریباً معادل همان عملیاتی هستند که به طور معمول با استفاده از عملگرها (مانند in
، delete
) یا متدهای Object
(مانند Object.keys()
، Object.defineProperty()
) انجام میدهیم. اما Reflect
این عملیات را در یک API واحد و سازگار گروهبندی میکند.
مزایای Reflect
- استانداردسازی:
Reflect
متدهایی برای انجام عملیات پیشفرض جاوا اسکریپت روی اشیاء فراهم میکند که به صورت تابعی و با استفاده از پارامترهای مشخص قابل فراخوانی هستند. این به جای استفاده از عملگرها که همیشه در یک تابع قابل استفاده نیستند، یک رویکرد تمیزتر ارائه میدهد. - سادگی و سازگاری با Proxy: متدهای
Reflect
دارای همان سیگنچر (signature) ورودی هستند کهtraps
درProxy
دارند، که استفاده از آنها را در داخلhandler
های پروکسی بسیار آسان و توصیه شده میکند. - بازگشت مقادیر منطقی: برخلاف برخی عملیات
Object
که در صورت شکست خطا پرتاب میکنند، متدهایReflect
معمولاً یک مقدار بولین (true/false) برمیگردانند که نشاندهنده موفقیت یا شکست عملیات است.
const myObject = {};
// معادل myObject.a = 1;
Reflect.set(myObject, 'a', 1);
console.log(myObject.a); // 1
// معادل 'a' in myObject;
console.log(Reflect.has(myObject, 'a')); // true
// معادل delete myObject.a;
Reflect.deleteProperty(myObject, 'a');
console.log(myObject.a); // undefined
// استفاده در Proxy (مثالی که در بالا هم بود)
const handler2 = {
get(target, property, receiver) {
console.log(`Accessing ${property}`);
return Reflect.get(target, property, receiver);
}
};
const proxy2 = new Proxy({ x: 10 }, handler2);
console.log(proxy2.x); // Accessing x \n 10
کاربردهای Proxy و Reflect
- Validation (اعتبارسنجی): اطمینان از اینکه مقادیر اختصاص داده شده به ویژگیها با قوانین خاصی مطابقت دارند.
- Logging و Debugging: ثبت تمام عملیات انجام شده روی یک شیء برای اهداف اشکالزدایی یا نظارت.
- Access Control: محدود کردن دسترسی به ویژگیها یا متدهای خاص بر اساس مجوزهای کاربر.
- Reactivity (واکنشگرایی): ساخت سیستمهایی که به طور خودکار به تغییرات دادهها واکنش نشان میدهند (مثل Vue.js).
- Auto-Populating Properties: ایجاد ویژگیها به صورت پویا در صورت درخواست.
- Memoization: کش کردن نتایج توابع با استفاده از پروکسی روی ورودیها.
- ORM (Object-Relational Mapping): ایجاد شیءهایی که به طور شفاف با یک پایگاه داده تعامل دارند.
Proxy
و Reflect
ابزارهای فوقالعاده قدرتمندی هستند که امکان دستکاری رفتار اشیاء در زمان اجرا را فراهم میکنند و پایه و اساس بسیاری از الگوهای طراحی پیشرفته و فریمورکهای مدرن جاوا اسکریپت را تشکیل میدهند. درک عمیق آنها شما را قادر میسازد به سطوح جدیدی از انتزاع و انعطافپذیری در کد جاوا اسکریپت دست یابید.
انتخاب ساختار داده مناسب و ملاحظات عملکردی
تا اینجا، ما به بررسی دقیق انواع دادهها و ساختارهای پیشرفته در جاوا اسکریپت پرداختیم: Symbol، BigInt، Map، Set، WeakMap، WeakSet، Typed Arrays، ArrayBuffer، Proxy و Reflect. هر کدام از اینها برای حل مسائل خاصی بهینهسازی شدهاند و درک زمان و نحوه استفاده از آنها برای نوشتن کدهای کارآمد، مقیاسپذیر و قابل نگهداری ضروری است.
راهنمای انتخاب ساختار داده
انتخاب ساختار داده مناسب به عوامل مختلفی بستگی دارد، از جمله:
- نوع دادهای که میخواهید ذخیره کنید: آیا اعداد بزرگ هستند؟ آیا نیاز به کلیدهای غیر رشتهای دارید؟ آیا فقط مقادیر منحصربهفرد میخواهید؟
- عملیات غالب: بیشتر چه عملیاتی را روی دادهها انجام خواهید داد؟ افزودن، حذف، جستجو، پیمایش، یا دستکاری بایتها؟
- ملاحظات حافظه: آیا نشت حافظه یک نگرانی است؟ آیا با دادههای باینری بزرگ کار میکنید؟
- کارایی (Performance): در مجموعه دادههای بزرگ، عملکرد عملیات اهمیت پیدا میکند.
در ادامه یک راهنمای خلاصه برای انتخاب ارائه شده است:
- Array (آرایه):
- کی استفاده کنیم:
- زمانی که نیاز به یک لیست مرتب از آیتمها دارید.
- زمانی که ترتیب عناصر اهمیت دارد.
- برای مجموعهای از آیتمها که ممکن است شامل تکراریها باشند.
- زمانی که عملیات اصلی شما شامل افزودن/حذف در انتها یا ابتدای لیست، دسترسی به عنصر با ایندکس، یا پیمایش خطی است.
- ملاحظات: عملیات جستجو (
indexOf
،includes
) برای آرایههای بزرگ میتواند کند باشد (O(n)). افزودن/حذف از وسط آرایه نیز میتواند گران باشد.
- کی استفاده کنیم:
- Object (شیء):
- کی استفاده کنیم:
- زمانی که نیاز به ذخیرهسازی جفتهای کلید-مقدار دارید و کلیدهای شما عمدتاً رشتهها یا Symbolهای ثابت هستند.
- برای مدلسازی موجودیتها با ویژگیهای مشخص (مثلاً یک کاربر با نام، سن، ایمیل).
- زمانی که به دسترسی سریع به مقادیر با استفاده از کلید آنها نیاز دارید (O(1) به طور متوسط).
- ملاحظات: کلیدها فقط میتوانند رشته یا Symbol باشند. ترتیب ویژگیها تضمین شده نیست (اگرچه در نسخههای جدید تا حدودی قابل پیشبینی است).
- کی استفاده کنیم:
- Map:
- کی استفاده کنیم:
- زمانی که نیاز به جفتهای کلید-مقدار دارید و کلیدها میتوانند از هر نوع دادهای (اشیاء، توابع، اعداد و غیره) باشند.
- زمانی که حفظ ترتیب درج کلیدها اهمیت دارد.
- برای بهبود عملکرد در سناریوهایی با افزودن/حذف مکرر کلیدها.
- به عنوان یک کش ساده یا برای memoization.
- ملاحظات: مصرف حافظه کمی بیشتر از Object برای مجموعه دادههای کوچک، اما برای مقادیر زیاد میتواند کارآمدتر باشد.
- کی استفاده کنیم:
- Set:
- کی استفاده کنیم:
- زمانی که نیاز به ذخیرهسازی مجموعهای از مقادیر منحصربهفرد دارید.
- برای حذف عناصر تکراری از آرایهها.
- برای بررسی سریع وجود یک عنصر در مجموعه (O(1) به طور متوسط).
- برای انجام عملیات مجموعهای مانند اجتماع، اشتراک و تفاضل.
- ملاحظات: عدم وجود ایندکس و دسترسی مستقیم به عنصر بر اساس موقعیت.
- کی استفاده کنیم:
- Symbol:
- کی استفاده کنیم:
- برای ایجاد کلیدهای ویژگی منحصربهفرد و جلوگیری از تداخل نام در اشیاء، به ویژه در کتابخانهها و فریمورکها.
- برای تعریف Symbolهای شناخته شده که رفتار داخلی اشیاء را تغییر میدهند (متا برنامهنویسی).
- ملاحظات: نمیتوان به طور مستقیم تکرار یا به رشته تبدیل کرد.
- کی استفاده کنیم:
- BigInt:
- کی استفاده کنیم:
- زمانی که نیاز به کار با اعداد صحیح بسیار بزرگتر از
Number.MAX_SAFE_INTEGER
(2^53 - 1
) یا کوچکتر ازNumber.MIN_SAFE_INTEGER
دارید. - در سناریوهایی مانند شناسههای پایگاه داده 64 بیتی، محاسبات رمزنگاری، یا سیستمهای مالی که دقت مطلق عددی حیاتی است.
- زمانی که نیاز به کار با اعداد صحیح بسیار بزرگتر از
- ملاحظات: نمیتوان با نوع
Number
ترکیب کرد و نیاز به تبدیل صریح دارد.
- کی استفاده کنیم:
- WeakMap / WeakSet:
- کی استفاده کنیم:
- زمانی که نیاز به ذخیرهسازی دادههای مرتبط با اشیاء (مانند metadata خصوصی) دارید، اما نمیخواهید این ذخیرهسازی مانع از جمعآوری شیء اصلی توسط Garbage Collector شود.
- برای مدیریت کشهایی که نیاز به تخلیه خودکار دارند.
- زمانی که نمیتوانید از کلیدها/عناصر غیرشیئی استفاده کنید.
- ملاحظات: قابلیت پیمایش (iteration) و دسترسی به
.size
را ندارند.
- کی استفاده کنیم:
- Typed Arrays (و ArrayBuffer/DataView):
- کی استفاده کنیم:
- زمانی که نیاز به کار با دادههای باینری با کارایی بالا دارید.
- در سناریوهایی مانند WebGL، پردازش صدا/تصویر، WebSockets، و تعامل با WebAssembly.
- برای محاسبات عددی سنگین که نیاز به حافظه پیوسته و دسترسی مستقیم به بایتها دارند.
- ملاحظات: فقط برای انواع عددی. نیاز به درک Endianness و ساختار داده باینری.
- کی استفاده کنیم:
- Proxy و Reflect:
- کی استفاده کنیم:
- برای پیادهسازی الگوهای متا برنامهنویسی، مانند اعتبارسنجی خودکار، لاگبرداری، کنترل دسترسی، یا سیستمهای واکنشگرا.
- زمانی که نیاز به سفارشیسازی رفتار پیشفرض اشیاء دارید.
- ملاحظات: استفاده نادرست میتواند کد را پیچیده کند. Overhead جزئی در عملکرد به دلیل رهگیری عملیات.
- کی استفاده کنیم:
ملاحظات عملکردی (Performance Considerations)
عملکرد هر ساختار دادهای به طور کلی به پیادهسازی موتور جاوا اسکریپت (V8 در Chrome و Node.js، SpiderMonkey در Firefox و غیره) و الگوریتمهای زیربنایی آن بستگی دارد. اما میتوانیم اصول کلی را در نظر بگیریم:
- پیچیدگی زمانی (Time Complexity - Big O Notation):
- جستجو (Lookup):
Object
وMap
(بر اساس کلید): به طور متوسط O(1).Set
(با.has()
): به طور متوسط O(1).Array
(باindexOf
/includes
): O(n) در بدترین حالت.
- افزودن/حذف (Insertion/Deletion):
Object
،Map
،Set
: به طور متوسط O(1).Array
(در انتها): O(1). (در ابتدا یا وسط): O(n) به دلیل نیاز به جابجایی عناصر.
- پیمایش (Iteration):
Array
،Map
،Set
: O(n).
درک پیچیدگی زمانی به شما کمک میکند تا بهترین ساختار را برای عملیات غالب خود انتخاب کنید، به خصوص زمانی که با حجم زیادی از دادهها سروکار دارید.
- جستجو (Lookup):
- مصرف حافظه (Memory Usage):
Object
ها ممکن است برای تعداد کمی از ویژگیها، سربار کمتری نسبت بهMap
داشته باشند.Map
وSet
میتوانند برای مجموعه دادههای بزرگتر ازObject
وArray
(که نیاز به بازسازی یا حذف تکراری دارند) کارآمدتر باشند.Typed Arrays
از حافظه فشردهتری استفاده میکنند زیرا بایتها به طور پیوسته و با نوع مشخص ذخیره میشوند، که برای دادههای عددی حجم بالا بسیار بهینه است.WeakMap
وWeakSet
به طور خاص برای بهینهسازی حافظه و جلوگیری از نشت حافظه در سناریوهای مدیریت چرخه حیات شیء طراحی شدهاند.
- پرهیز از بهینهسازی زودهنگام (Premature Optimization):
همیشه قبل از اینکه بهینهسازیهای پیچیده انجام دهید، پروفایلسازی (profiling) کنید. گاهی اوقات، خوانایی و سادگی کد مهمتر از افزایش عملکرد جزئی است که شاید هرگز مورد نیاز نباشد. از ساختار دادهای استفاده کنید که منطقیترین و خواناترین راه حل را ارائه میدهد، مگر اینکه پروفایلسازی نشان دهد که در آن بخش خاصی از کد گلوگاه عملکردی وجود دارد.
نتیجهگیری
جاوا اسکریپت از یک زبان ساده اسکریپتنویسی به یک اکوسیستم قدرتمند و چندوجهی تبدیل شده است. بخش عمدهای از این تحول به دلیل تکامل مستمر در زمینه مدیریت دادهها و معرفی انواع و ساختارهای دادهای پیشرفته است. درک عمیق Symbol، BigInt، Map، Set، WeakMap، WeakSet، Typed Arrays، ArrayBuffer، Proxy و Reflect، شما را به ابزارهایی مجهز میکند که میتوانید با آنها چالشهای پیچیده برنامهنویسی را حل کنید، کدهای بهینهتر و مقیاسپذیرتر بنویسید، و از آخرین قابلیتهای زبان جاوا اسکریپت بهرهمند شوید. با تسلط بر این مفاهیم، نه تنها یک برنامهنویس جاوا اسکریپت ماهرتر خواهید بود، بلکه قادر خواهید بود راهحلهای نوآورانهای برای مسائل دنیای واقعی ارائه دهید. همیشه به یاد داشته باشید که انتخاب درست ساختار داده، ستون فقرات یک نرمافزار کارآمد و قابل نگهداری است.
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان