مواقع زیادی پیش میآیند که خود را در معرض کار با آرایهها میبینید. اما نگران این مسئله نیستید؛ زیرا این کار را قبلا هم انجام دادهاید و سخت هم نبوده است. همچنین گزینههای زیادی دارید. مثلا میتوانید از حلقههای معمولی، map، reduce و filter، و یا iteratorها استفاده کنید.
Iterator یک الگوی طراحی است که ما را قادر میسازد تا یک لیست یا مجموعه را بپیماییم. در JavaScript، این موارد به عنوان آبجکت پیادهسازی میشوند. قبل از این که به جزئیات وارد شویم، در اینجا یک مثال ساده را میبینید:
const arr = [1, 2, 3, 4, 5];
for (const num of arr) {
console.log(num);
}
با استفاده از حلقه for…of، میتوانید بر روی هر آبجکتی که پروتکل iterable را پیادهسازی میکند، iterate کنید.
پروتکل Iterable
برای پیروی از این پروتکل، آبجکت مورد نظر باید متد خاصی به نام @@iterator را تعریف کند، که هیچ آرگومانی نمیگیرد و یک آبجکت را بر میگرداند. این آبجکت برگردانده شده، خود باید از پروتکل iterator پیروی کند.
پروتکل Iterator
برای پیروی از این پروتکل، آبجکت مورد نظر باید متدی به نام next را تعریف کند، که خودش آبجکتی با دو ویژگی را بر میگرداند:
- Value: آیتم فعلی در تکرار (iteration)
- Done: یک مقدار boolean، که وضعیت کامل شدن یا نشدن تکرار را نشان میدهد. done=true یعنی این که تکرار کامل شده است.
آرایه، رشته، Map و Set از پروتکل iterator پیروی میکنند.
پیادهسازی پروتکلها
در اینجا، تابعی میبینید که یک آبجکت iterable را بر میگرداند، که ما را قادر میسازد تا بر روی اولین عدد طبیعی n، iterate کنیم:
function numbers(till = 100) {
let i = 0;
const iteratorFx = () => {
const iterator = {
next() {
i += 1;
if (i <= till) {
return { done: false, value: i };
}
return { done: true };
},
};
return iterator;
};
return {
[Symbol.iterator]: iteratorFx,
};
}
const numbersTill10 = numbers(10);
for (const i for numbersTill10) {
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}
متد @@iterator فقط یک بار و در ابتدای حلقه for…of فراخوانی میشود. پس کد زیر نیز مشابه مورد بالا است:
for (const i for numbers(10)) {
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}
از آنجایی که ما میدانیم این آبجکت متدی به نام next دارد که جزئیات تکرار را پیادهسازی میکند، میتوانیم به سادگی خودمان این متد را فراخوانی کنیم.
const numbersTill5 = number(5);
numbersTill5.next(); // { done: false, value : 1 }
numbersTill5.next(); // { done: false, value : 2 }
numbersTill5.next(); // { done: false, value : 3 }
numbersTill5.next(); // { done: false, value : 4 }
numbersTill5.next(); // { done: false, value : 5 }
numbersTill5.next(); // { done: true }
این میتواند با انتقال مقادیر به متد next، برای کنترل خارجی تکرار استفاده شود.
میتوانیم به صورت بالا، iteratorهای سفارشی خود را پیادهسازی کنیم. گرچه، JavaScript راه دیگری برای ساخت iterableها نیز فراهم کرده است.
مولدها
مولدها توابع خاصی هستند که وقتی فراخوانی میشوند، آبجکتی به نام Generator را بر میگردانند. آبجکت generator از پروتکلهای تکرار پیروی میکند. پس برای پیادهسازی مثال بالا با استفاده از مولدها، به این صورت عمل میکنیم:
function* generateNumbers(till = 100) {
let i = 1;
while (i <= till) {
yield i;
i += 1;
}
}
const numbersTill10 = generateNumbers(10); // iterator
مقدار ارسال شده توسط yield، (که در اینجا i است) مقدار ذخیره شده در آبجکت بازگردانده شده توسط متد next خواهد بود، و وقتی که مولد کار خود را تمام میکند، مقدار { done: true } را بر میگرداند.
از مثال بالا کاملا مشخص است که مولدها راه مختصری برای ساخت iterableها فراهم میکنند. آنها پروتکلها را چکیدهسازی میکنند، و ما فقط نیاز است که نگران منطق تکرار باشیم.
نتیجه گیری
از آنجایی که این پست را با پیموندن آرایهها شروع کردیم، بهتر است مثالی شامل آرایهها نیز داشته باشیم. آرایهها به طور پیشفرض iterable هستند، پس ما یک value mapper میسازیم که iterable باشد:
function* mapOver(arr, mapper = (v) => v) {
for (let i = 0; i < arr.length; i += 1) {
yield mapper(arr[i]);
}
}
const twices = mapOver([...numbers(5)], (v) => v + 2);
for (const num of twices) {
// 2, 4, 6, 8, 10
}
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید