این رفتارهای اساسی را درک کنید و برنامه نویسی جاوا اسکریپت خود را به سطح بعدی ببرید.
تکرار بر روی مجموعه های داده با استفاده از حلقه های سنتی می تواند به سرعت دست و پا گیر و کند شود، به خصوص زمانی که با حجم عظیمی از داده ها سروکار داریم.
مولدها و تکرارکننده های جاوا اسکریپت راه حلی برای تکرار کارآمد در مجموعه های بزرگ داده ارائه می دهند. با استفاده از آنها، می توانید جریان تکرار را کنترل کنید، مقادیر را یک به یک ارائه دهید، و روند تکرار را متوقف کرده و از سر بگیرید.
در اینجا شما اصول اولیه و داخلی یک تکرار کننده جاوا اسکریپت و اینکه چگونه می توانید یک تکرار کننده را به صورت دستی و با استفاده از یک مولد ایجاد کنید، پوشش خواهید داد.
تکرار کننده جاوا اسکریپت
iterator یک شی جاوا اسکریپت است که پروتکل iterator را پیاده سازی می کند. این اشیا با داشتن روش بعدی این کار را انجام می دهند. این متد یک شی را برمی گرداند که رابط IteratorResult را پیاده سازی می کند.
رابط IteratorResult از دو ویژگی تشکیل شده است: done و value. خاصیت done یک بولی است که اگر تکرار کننده بتواند مقدار بعدی را در دنباله خود تولید کند false یا اگر تکرار کننده دنباله خود را کامل کرده باشد true را برمی گرداند.
ویژگی value یک مقدار جاوا اسکریپت است که توسط تکرار کننده در طول دنباله آن برگردانده می شود. هنگامی که یک تکرار کننده دنباله خود را کامل می کند (در صورت انجام === true)، این ویژگی تعریف نشده برمی گردد.
همانطور که از نام آن پیداست، تکرارکنندهها به شما اجازه میدهند تا روی اشیاء جاوا اسکریپت مانند آرایهها یا نقشهها «تکرار» کنید. این رفتار به دلیل پروتکل تکرارپذیر امکان پذیر است.
در جاوا اسکریپت، پروتکل تکرارپذیر یک روش استاندارد برای تعریف اشیایی است که میتوانید آنها را تکرار کنید، مانند حلقه for…of.
مثلا:
const fruits = ["Banana", "Mango", "Apple", "Grapes"];
for (const iterator of fruits) {
console.log(iterator);
}
/*
Banana
Mango
Apple
Grapes
*/
این مثال با استفاده از حلقه for…of روی آرایه میوه ها تکرار می شود. در هر تکرار، مقدار فعلی را در کنسول ثبت می کند. این امکان پذیر است زیرا آرایه ها تکرارپذیر هستند.
برخی از انواع جاوا اسکریپت، مانند آرایهها، رشتهها، مجموعهها و نقشهها، تکرارپذیرهای داخلی هستند زیرا آنها (یا یکی از اشیاء زنجیره اولیه خود) یک روش @@iterator را پیادهسازی میکنند.
انواع دیگر مانند Objects به طور پیش فرض قابل تکرار نیستند.
مثلا:
const iterObject = {
cars: ["Tesla", "BMW", "Toyota"],
animals: ["Cat", "Dog", "Hamster"],
food: ["Burgers", "Pizza", "Pasta"],
};
for (const iterator of iterObject) {
console.log(iterator);
}
// TypeError: iterObject is not iterable
این مثال نشان میدهد که چه اتفاقی میافتد وقتی میخواهید روی یک شی که تکرارپذیر نیست تکرار کنید.
ساختن یک شیء تکرارپذیر
برای اینکه یک شی قابل تکرار باشد، باید یک متد Symbol.iterator را روی شی پیاده سازی کنید. برای تبدیل شدن به تکرار، این متد باید شیئی را برگرداند که رابط IteratorResult را پیاده سازی کند.
این
نماد. تکرار کننده
نماد همان هدف را دارد
@@اشاره گر
و می تواند به جای یکدیگر در “مشخصات” استفاده شود اما نه در کد به عنوان
@@اشاره گر
نحو جاوا اسکریپت معتبر نیست.
بلوکهای کد زیر نمونهای از نحوه تکرارپذیر کردن یک شی با استفاده از iterObject را ارائه میدهند.
ابتدا متد Symbol.iterator را با استفاده از اعلان تابع به iterObject اضافه کنید.
اینطوری:
iterObject[Symbol.iterator] = function () {
// Subsequent code blocks go here...
}
در مرحله بعد، باید به تمام کلیدهای موجود در شیئی که میخواهید قابل تکرار بسازید دسترسی داشته باشید. شما می توانید با استفاده از روش Object.keys که آرایه ای از ویژگی های قابل شمارش یک شی را برمی گرداند، به کلیدها دسترسی پیدا کنید. برای برگرداندن آرایه ای از کلیدهای iterObject، این کلمه کلیدی را به عنوان آرگومان به Object.keys ارسال کنید.
مثلا:
let objProperties = Object.keys(this)
دسترسی به این آرایه به شما امکان می دهد تا رفتار تکرار شی را تعریف کنید.
در مرحله بعد، باید تکرارهای شی را پیگیری کنید. شما می توانید با استفاده از متغیرهای شمارنده به این هدف برسید.
مثلا:
let propertyIndex = 0;
let childIndex = 0;
شما از اولین متغیر شمارنده برای پیگیری ویژگی های شی و دومی برای پیگیری فرزندان ویژگی استفاده خواهید کرد.
در مرحله بعد، باید روش بعدی را پیاده سازی و برگردانید.
اینطوری:
return {
next() {
// Subsequent code blocks go here...
}
}
در روش بعدی، شما باید یک مورد لبه را مدیریت کنید که زمانی اتفاق میافتد که کل شیء دوباره تکرار شده باشد. برای مدیریت edge case، باید یک شی را با مقدار undefined و تنظیم شده به true برگردانید.
اگر این مورد رسیدگی نشود، تلاش برای تکرار روی شی منجر به یک حلقه بی نهایت می شود.
در اینجا نحوه رسیدگی به لبه کیس آمده است:
if (propertyIndex > objProperties.length - 1) {
return {
value: undefined,
done: true,
};
}
در مرحله بعد، باید با استفاده از متغیرهای شمارندهای که قبلاً اعلام کردهاید، به ویژگیهای شی و عناصر فرزند آنها دسترسی داشته باشید.
اینطوری:
// Accessing parent and child properties
const properties = this[objProperties[propertyIndex]];
const property = properties[childIndex];
در مرحله بعد، باید منطقی را برای افزایش متغیرهای شمارنده پیاده سازی کنید. زمانی که هیچ عنصر دیگری در آرایه یک ویژگی وجود ندارد، منطق باید ایندکس کودک را بازنشانی کند و به ویژگی بعدی در شی منتقل شود. علاوه بر این، اگر هنوز عناصری در آرایه ویژگی فعلی وجود دارد، باید childIndex را افزایش دهد.
مثلا:
// Index incrementing logic
if (childIndex >= properties.length - 1) {
// if there are no more elements in the child array
// reset child index
childIndex = 0;
// Move to the next property
propertyIndex++;
} else {
// Move to the next element in the child array
childIndex++
}
در نهایت، یک شی با ویژگی done را روی false و ویژگی مقدار را به عنصر فرزند فعلی در تکرار برگردانید.
مثلا:
return {
done: false,
value: property,
};
تابع Symbol.iterator تکمیل شده شما باید شبیه بلوک کد زیر باشد:
iterObject[Symbol.iterator] = function () {
const objProperties = Object.keys(this);
let propertyIndex = 0;
let childIndex = 0;
return {
next: () => {
//Handling edge case
if (propertyIndex > objProperties.length - 1) {
return {
value: undefined,
done: true,
};
}
// Accessing parent and child properties
const properties = this[objProperties[propertyIndex]];
const property = properties[childIndex];
// Index incrementing logic
if (childIndex >= properties.length - 1) {
// if there are no more elements in the child array
// reset child index
childIndex = 0;
// Move to the next property
propertyIndex++;
} else {
// Move to the next element in the child array
childIndex++
}
return {
done: false,
value: property,
};
},
};
};
اجرای یک حلقه for…of در iterObject پس از این پیاده سازی، خطایی ایجاد نمی کند زیرا یک متد Symbol.iterator را پیاده سازی می کند.
پیاده سازی دستی تکرار کننده ها، همانطور که در بالا انجام دادیم، توصیه نمی شود، زیرا بسیار مستعد خطا است، و مدیریت منطق ممکن است سخت باشد.
مولدهای جاوا اسکریپت
یک مولد جاوا اسکریپت تابعی است که میتوانید در هر نقطهای از آن توقف کرده و اجرای آن را از سر بگیرید. این رفتار به آن اجازه می دهد تا در طول زمان دنباله ای از مقادیر را تولید کند.
یک تابع مولد، که تابعی است که یک Generator را برمی گرداند، جایگزینی برای ایجاد تکرارکننده ارائه می دهد.
شما می توانید یک تابع مولد را به همان روشی که یک اعلان تابع در جاوا اسکریپت ایجاد می کنید ایجاد کنید. تنها تفاوت این است که باید یک ستاره (*) به کلمه کلیدی تابع اضافه کنید.
مثلا:
function* example () {
return "Generator"
}
هنگامی که یک تابع معمولی را در جاوا اسکریپت فراخوانی می کنید، مقدار مشخص شده توسط کلمه کلیدی بازگشتی یا تعریف نشده را برمی گرداند. اما یک تابع مولد هیچ مقداری را بلافاصله بر نمی گرداند. یک شی Generator را برمی گرداند که می توانید آن را به یک متغیر اختصاص دهید.
برای دسترسی به مقدار فعلی تکرار کننده، متد بعدی را در شی Generator فراخوانی کنید.
مثلا:
const gen = example();
console.log(gen.next()); // { value: 'Generator', done: true }
در مثال بالا، ویژگی value از یک کلمه کلیدی بازگشتی به دست آمده است، که عملاً مولد را خاتمه می دهد. این رفتار عموماً در مورد توابع ژنراتور نامطلوب است، زیرا آنچه آنها را از توابع عادی متمایز می کند، توانایی توقف و شروع مجدد اجرا است.
کلمه کلیدی بازده
کلیدواژه yield با توقف اجرای یک تابع مولد و برگرداندن مقداری که به دنبال آن است، راهی برای تکرار مقادیر در ژنراتورها فراهم می کند.
مثلا:
function* example() {
yield "Model S"
yield "Model X"
yield "Cyber Truck"
return "Tesla"
}
const gen = example();
console.log(gen.next()); // { value: 'Model S', done: false }
در مثال بالا، وقتی متد بعدی در مولد نمونه فراخوانی میشود، هر بار که با کلمه کلیدی yield مواجه میشود، مکث میکند. خاصیت done نیز تا زمانی که با کلمه کلیدی بازگشتی مواجه شود روی false تنظیم می شود.
با چند بار فراخوانی متد بعدی در ژنراتور مثال برای نشان دادن این موضوع، خروجی زیر را خواهید داشت.
console.log(gen.next()); // { value: 'Model X', done: false }
console.log(gen.next()); // { value: 'Cyber Truck', done: false }
console.log(gen.next()); // { value: 'Tesla', done: true }
console.log(gen.next()); // { value: undefined, done: true }
همچنین می توانید با استفاده از حلقه for…of روی یک شی Generator تکرار کنید.
مثلا:
for (const iterator of gen) {
console.log(iterator);
}
/*
Model S
Model X
Cyber Truck
*/
استفاده از Iterators و Generator
اگرچه تکرار کننده ها و مولدها ممکن است مفاهیمی انتزاعی به نظر برسند، اما اینطور نیستند. آنها می توانند هنگام کار با جریان های داده بی نهایت و مجموعه داده ها مفید باشند. همچنین می توانید از آنها برای ایجاد شناسه های منحصر به فرد استفاده کنید. کتابخانه های مدیریت دولتی مانند MobX-State-Tree (MST) نیز از آنها در زیر پوشش استفاده می کنند.