Generators در جاوا اسکریپت


در سال 2015 یک سری ویژگی های جدید مثل arrow functions, classes و modules به جامعه برنامه نویسان معرفی شد که به سرعت بین برنامه نویسان محبوب شد و در برنامه های خود از این ویژگی ها استفاده کردند.برخی دیگر از ویژگی های امروزه کمتر توسط توسعه دهندگان شناخته شده و مورد استفاده قرار می گیرد. آیا می دانید generators چیست؟ این دقیقاً همان چیزی است که ما در این مقاله خواهیم آموخت.
معرفی مختصر
قبل از اینکه مفهوم generators در جاوا اسکریپت رو درک کنیم ابتدا باید تعریف و مفاهیم یک سری موارد دیگه رو یاد بگیریم.Generators کلا در مورد iteration است.حالا بیایید ببینیم چگونه iteration در جاوا اسکریپت تکامل یافته است.به این ترتیب ، می توانیم یاد بگیریم که generators هنگام استفاده صحیح چه مزایایی می توانند ایجاد کنند.
Iteration
در جاوا اسکریپت آرایه ها بیشتر چیزی هستند که توسط مردم iterate over می شوند (Iteration تکرار یک فرآیند به منظور تولید یک دنباله از نتایج است).بنابراین ، بیایید آرایه ها را به عنوان مثال در این مقاله استفاده کنیم.چندین روش برای iterating وجود دارد که 4 روش متداول رو اینجا بیان می کنیم:
1)استفاده از forEach
1 |
someArray.forEach(item => console.log(item)); |
2)استفاده از while loops
1 2 3 4 5 6 |
let i = 0; while (i < someArray.length) { const item = someArray[i]; console.log(item); i++; } |
3)استفاده از for loops
1 2 3 4 |
for (let i = 0; i < someArray.length; i++) { const item = someArray[i]; console.log(item); } |
4)استفاده از for of loops
1 2 3 |
for (let item of someArray) { console.log(item); } |
به نظر می رسد forEach و for of بهترین گزینه ها به نظر می رسند.forEach در Array’s prototype تعریف شده است. for-of loop هم اختیارات فوق العاده ای در اختیار ما می گذارد،به مثال زیر توجه کنید:
1 2 3 4 5 6 7 8 9 |
for (let letter of 'abcdef') { console.log(letter); } //=> 'a' //=> 'b' //=> 'c' //=> 'd' //=> 'e' //=> 'f' |
Iterators
هر object که iterator protocol را پیاده سازی کند،iterator است.iterator protocol برای درک و پیروی از آن بسیار ساده است.iterator انتظار دارد که object یک next() method داشته باشد.زمانی که فراخوانی فراخوانی شود باید به item بعدی در دنباله حرکت کرده و یک object را برگرداند.این object باید حداقل دو properties داشته باشد: done و Value.
done property یک مقدار boolean است که تشخصی می دهد iterator در انتها است یا نه و value property همانطور که از نام اون پیداست value موقعیت فعلی را نشان می دهد.
بیایید به مثال زیر توجه کنید.این تابع یک iterators ایجاد می کند.
1 2 3 4 5 6 7 8 9 10 11 12 |
function buildSquaredNumbersIterator(from = 1, to = 10) { let count = from - 1; return { next: () => { if (count >= to) return { done: true }; count++; return { value: count * count, done: false }; } }; } |
برای ایجاد iterator با استفاده از تابع بالا، شما کافیه که فقط اون رو فراخوانی کنید:
1 2 3 4 5 6 7 8 9 10 |
const iterator = buildSquaredNumbersIterator(2, 9); console.log(iterator.next()); //=> { value: 4, done: true } console.log(iterator.next()); //=> { value: 9, done: true } console.log(iterator.next()); //=> { value: 16, done: true } console.log(iterator.next()); //=> { value: 25, done: true } console.log(iterator.next()); //=> { value: 36, done: true } console.log(iterator.next()); //=> { value: 49, done: true } console.log(iterator.next()); //=> { value: 64, done: true } console.log(iterator.next()); //=> { value: 81, done: true } console.log(iterator.next()); //=> { done: false } |
به طور طبیعی، شما می توانید به سادگی از یک loop برای iterate استفاده کنید:
1 2 3 4 5 6 7 |
const iterator = buildSquaredNumbersIterator(2, 9); let res = iterator.next(); while (!res.done) { console.log(result.value); res = it.next(); } |
خوب .آیا ما می توانیم از iterator سفارشی خود با for-of loop استفاده کنیم؟پاسخ نه است. برای سازی گاری با for-of loops باید object از iterable protocol پیروی کند.
Iterables
برای اینکه یک object به عنوان iterable تلقی شود باید iterable protocol را پیاده سازی کند.انتظار دارد که object یک method داشته باشد که key آن مقدار Symbol.iterator است.این method بدون هیچ arguments فراخوانی می شود و باید یک iterator را برگرداند.
بیایید iterator سفارشی خود را برای تطبیق با این protocol تغییر دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function buildSquaredNumbersIterator(from = 1, to = 10) { let count = from - 1; return { next: () => { if (count >= to) return { done: true }; count++; return { value: count * count, done: false }; }, [Symbol.iterator]: function() { return this; } }; } |
تنها تفاوت در اینجا یک method با Symbol.iterator key است.اما چرا فقط this را برمی گرداند؟همانطور که دیدیم iterable protocol انتظار دارد این method ویژه یک iterator را برگرداند.توجه داشته باشید که ما این method را به یک object اضافه شده به iterator protocol اضافه می کنیم.
به این ترتیب ما می توانیم به سادگی this را برگردانیم که به خود object اشاره خواهد کرد.با انجام این کار iterator ما تبدیل به یک iterable می شود.این کار همچنین باعث می شود تا ما بتوانیم از iterator سفارشی خود در for-of loops استفاده کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const numbers = buildSquaredNumbersIterator(2, 9); for (let n of numbers) { console.log(n); } //=> 4 //=> 9 //=> 16 //=> 25 //=> 36 //=> 49 //=> 64 //=> 81 |
Generators
iterable/iterator سفارشی ما به خوبی کار می کند.با این وجود ما می توانستیم با استفاده از توابع ES2015’s generator عملکردی دقیقا مشابهی را پیاده سازی کنیم.اما قبل از اینکه iterator را بازسازی کنیم.بهتره درک کنیم که generator چیست:
- Generators با استفاده از یک نوع حاص function که generator function نام گذاری می شود،ایجاد می شوند.
- generator هر دو iterable و iterator است.
- حتی زمانی که شما generator function را فراخوانی کنید.دستورالعمل های آن اجرا نمی شوند.
- زمانی که next() method فراخوانی می شود.اجرای آن تا زمانی ادامه می یابد تا یک instruction پیدا شود.
- زمانی یک instruction یافت می شود و value برگشت داده می شود، اجرای آن متوقف می شود و پس از آن منتظر فراخوانی جدید با next() method است.
- این چرخه تا زمانی که دستورالعمل بیشتری یافت نشود، تکرار می شود.
در اینجا یک generator function ساده وجود دارد :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function *buildHardcodedValuesGenerator() { console.log('One'); yield 1; console.log('Two'); yield 2; console.log('Three'); yield 3; console.log('The end'); } const generator = buildHardcodedValuesGenerator(); console.log('Returned value:', generator.next().value); //=> 'One' //=> Returned value: 1 console.log('Returned value:', generator.next().value); //=> 'Two' //=> Returned value: 2 console.log('Returned value:', generator.next().value); //=> 'Three' //=> Returned value: 3 console.log('Returned value:', generator.next().value); //=> 'The end' //=> Returned value: undefined |
همانطور که می بینید در اولین فراخوانی بدنه generator function ما فراخوانی نمی شود.همچنین واضح است که هر بار که متد () next را فراخوانی می کنیم ، اجرای آن ادامه می یابد تا نتیجه بعدی پیدا شود.هنگامی که این اتفاق می افتد ، اجرای آن را متوقف می کند و yields را به عنوان value می دهد. هنگامی که yields بیشتری یافت نشد ، متد next () یک شی با مقدار undefined و done flage با مقدار true را برمی گرداند.
حالا ببینید که چطور می توان یک iterator تبدیل به generator شود:
1 2 3 4 5 |
function *buildSquaredNumbersIterator(from = 1, to = 10) { for (let count = from; count <= to; count++) { yield count * count; } } |
به نظر می رسد که این قطعه کد از ورژن قبلی خیلی ساده تر است.لازم نیست که internal stat را کنترل کنیم یا به پروتکل پایبند باشیم.ما فقط yield را برای هر value که برگشت داده می شود،فراخوانی می کنیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const numbersGenerator = buildSquaredNumbersIterator(4, 11); for (let number of numbersGenerator) { console.log(number); } //=> 16 //=> 25 //=> 36 //=> 49 //=> 64 //=> 81 //=> 100 //=> 121 |
دیدگاهتان را بنویسید