بررسی iterable و iterator در جاوا اسکریپت

توی این نوشته تکرارپذیر (iterable) و تکرار شونده (iterator) در جاوا اسکریپت رو بررسی کردم. البته در کنار این مباحث، به موارد دیگه ای هم اشاره کردم و اینکه سعی کردم بیشتر از توضیح، کد ببینید.

تکرارپذیر شوندگان! بررسی iterable و iterator در جاوا اسکریپت
تکرارپذیر شوندگان! بررسی iterable و iterator در جاوا اسکریپت


پیش نیاز: شما باید از قبل با مفاهیم اولیه جاوا اسکریپت مثل شیء، آرایه و حلقه آشنا باشید.

نکته: در طول متن خروجی های هر کد رو با :o// نشون دادم.

بیاید با یک مثال شروع کنیم.

https://gist.github.com/behnamazimi/b65372122d232ffd10448a04bc183b30

در مثال بالا آرایه data یک تکرارپذیر (iterable) است. توی جاوا اسکریپت همه چیز تکرارپذیر نیست ولی اکثر موجودیت ها به صورت پیش فرض این ویژگی رو دارن. تنها تکرارپذیر های مادرزاد جاوا اسکریپت آرایه ها و شبه آرایه ها (Array-likes) هستن و for...of فقط روی اینا قابل اجراست.

شبه آرایه ها Object هایی هستند که از آرایه بودن فقط سیستم دسترسی ایندکسی و ویژگی length بهشون رسیده. Strings، TypedArray، Map، Set و یا یک لیست از DOM Node ها شبه آرایه های JS هستند.

میشه اینطوری توضیح داد که یک تکرارپذیر (iterable) ساختار داده ای هستش که امکان دسترسی متوالی به عناصر داخلیش رو بهمون میده و وقتی داخل حلقه for...of قرار اجرا میشه، تک تک عناصرش رو برامون برمیگردونه.

هر iterable دارای یک ویژگی (peoperty) به اسم iterator (تکرارشونده) است. iterator یک نشانگر است که بر روی عناصر حرکت میکنه تا اون توالی رو ایجاد کنه.

حرکت iterator بر روی عناصر به این صورت است که هر iterator یک متد از پیش تعریف شده به اسم next داره. در هر بار فراخوانی این متد، iterator مقدار عنصری که روی اون قرار داره رو به شکل `{value:..., done:true/false}` برمیگردونه و میره روی عنصر بعدی.

در این Object که از فراخوانی next به دست میاد، value مقدار عنصر فعلی که نشانگر (iterator) بر روی اون قرار داره هستش و done هم نشون میده که iterator به انتهای حلقه رسیده یا نه.

در ادامه ساختار داده ای که بالاتر بهش اشاره کردم رو شبیه سازی کردم. یعنی یک تکرارپذیر شونده ساختم :دی

https://gist.github.com/behnamazimi/564411c371d10e480fd6f956247594cf#file-iterables-code-02-js

الان برای ساختاری داده ای که در بالا نوشتم، میتونیم next رو صدا بزنیم و باعث حرکت رو به جلوی توالی بشیم. برای مثال میتونیم اون رو داخل یک حلقه به کار ببریم. مثل این:

https://gist.github.com/behnamazimi/2b6deb3090dc61873636aad31eee6ec0#file-iterables-code-03-js

و یا میتونیم خارج از حلقه و هر موقع که خودمون خواستیم next رو صدا بزنیم و توالی عناصر دادمون رو ادامه بدیم. مثل این:

https://gist.github.com/behnamazimi/e952905d8dcef0e1343eadfebc088510#file-iterables-code-04-js

مکانیزمی که حلقه ی for...of داره اینه که تا وقتی که مقدار done مساوی با true نشه تکرار (فراخوانی متد next) رو ادامه میده. یعنی مثل چیزی که توی مثال حلقه while در بالا انجام دادیم.

تا الان با مفهوم و ساختار iterator آشنا شدیم و یک ساختار داده تکرارپذیر درست کردیم و منطق حلقه رو براش پیاده کردیم. این عمل برای iterator های build-in جاوااسکریپت هم تقریبا مثل کاریه که من توی مثال انجام دادم. اما اگه بخوایم از for...of برای ایجاد حلقه روی ساختار دادمون استفاده کنیم چی؟ یعنی مثل این:

https://gist.github.com/behnamazimi/be35b5f133e703c991818a0e4f211663#file-iterables-code-05-js

نتیجه ای که کد بالا بهمون میده یک خطاست. یک خطا با این عنوان که "data تکرارپذیر نیست!" ولی چطور ممکنه؟! ما که ساختار دادمون توالی داره و حتی تونسته بودیم از اون توالی استفاده کنیم!

پارانتز باز:

با Symbol چقدر آشنایی دارید؟ اینجا قصد ندارم Symbol رو کامل براتون شرح بدم ولی یه نکاتی هست که قبل از ادامه باید بهش اشاره بکنیم. اگر بخواید میتونید از این قسمت رد بشید.

symbol یک نوع داده اولیه است. مثل String یا Number یا Boolean.

هر سمبلی که از فراخوانی Symbol() برگردونده همیشه منحصر به فرد (unique) است. یعنی جاوا اسکریپت unique بودن تمام symbol هارو تضمین میکنه. در واقع هیچ سمبلی نمیتونه وجود داشته باشه که حتی با یک سمبل دیگه برابر باشه.

هر سمبل میتونه یه توضیح هم داشته باشه که همیشه همراهش دیده میشه ولی اینکه دو سمبل توضیح یکسان داشته باشند دلیل بر برابر بودنشون نیست.

https://gist.github.com/behnamazimi/fee81c70a07e1d688fb5f26c4e2ab834#file-iterables-code-06-js

ار سمبل ها معمولا به عنوان شناسه property ها در Object استفاده میشه. برای مثال:

https://gist.github.com/behnamazimi/0bd80df03d0f2d20fb073e83f8969b08#file-iterables-code-07-js

در این مثال ما یک سمبل به اسم SymblOne تعریف کردیم و از اون به عنوان کلید یکی از property های آبجکتمون استفاده کردیم. الان فقط در صورتی که اون symbolOne رو داشته باشیم میتونیم به ویژگی هدف دسترسی داشته باشیم. در غیر این صورت اون ویژگی برامون غیر قابل دسترس خواهد بود.

https://gist.github.com/behnamazimi/899fc2f4058a60e58971157236f05d83#file-iterables-code-08-js

به کد بالا دقت کنید، من از یک سمبل به عنوان کلید استفاده کردم ولی دیگه به صورت مستقیم نمیتونم به اون داده دسترسی داشته باشم. راه غیر مستقیم دسترسی به دادمون هم استفاده از Object.getOwnPropertySymbols() هستش که یک آرایه از سمبل های تعریف شده داخل object رو برمیگردونه. مانند مثال پایین:

https://gist.github.com/behnamazimi/fe2cc3565b7fe3300adab7a57aebac60#file-iterables-code-09-js

سمبل های شناخته شده

بعضی از سمبل ها هم وجود دارند که جاوااسکریپت توی کد پایه خودش تعریف و استفاده کرده و در دسترس ما هم قرار داده. مثل Symbol.iterator و یا مثل Symbol.replace و خیلی سمبل دیگه که از هر جای کدمون صداش بزنیم، یک سمبل منحصر به فرد رو برامون برمیگردونه.

همینقدر توضیح در مورد سمبل ها کافیه به نظرم. الان موضوع اصلیمون رو ادامه بدیم.

پارانتز بسته!

نکته اینجاست که جاوااسکریپت، یا بهتره بگم for...of توی ساختار داده ما دنبال متد next نمیگرده. بلکه به دنبال سمبل Symbol.iterator میگرده. چون توی کدبیسش کلید دسترسی به iterable هر ساختار داده ای را همین سمبل تعریف کرده. این یعنی چی؟! یعنی اینکه میشه به iterator همه ی iterbale های جاوااسکریپت به صورت مستقیم با استفاده از Symbol.iterator دسترسی داشت و بیرون از حلقه ها ازشون برای توالی روی عناصر استفاده کرد.

اگر ما بخوایم توی مثال پیاده سازی ساختار داده که در بالا داشتیم، به جای متد next از سمبل Symbol.iterator استفاده کنیم، کد باید به شکل زیر تغییر بکند.

https://gist.github.com/behnamazimi/d7b73237abdea4e48bc00aa7f2c7f609#file-iterables-code-10-js

ولی اگه کد بالا رو در حلقه for...of بیارید دوباره خطا میده و اینبار خطا مربرط به متد next هست!

نکته ای که باید بهش توجه کنید اینه که، iterator هر iterable یک تابع هست و فراخوانی اون تابع متد next رو برمیگردونه. بزارید مثال اول مقاله رو با استفاده از Symbol.iterable بازنویسی کنیم:

https://gist.github.com/behnamazimi/34c3c3d49b0f41a053c29ef4bf2bb56f#file-iterables-code-11-js

همونطور که مشاهده میکنید، ما iterator داده رو که یک تابع هست فراخوانی کردیم و نتیجه رو در `iterator` قرار دادیم. و در هر بار متد next اش رو صدا زدیم تا روی عناصر حرکت کنیم.

پس با این حساب اگر بخوایم ساختار داده خودمون رو داخل حلقه for...of بزاریم، باید iterator اش متد next رو برگردونه و در نتیجه اجرای اون متد هست که توالی برقرار خواهد شد. به کد زیر دقت کنید.

https://gist.github.com/behnamazimi/f34c3dc0110200c257f612338cbf8d37#file-iterables-code-12-js

ولی! آیا این ساختار داده ای که نوشتیم کامل هست؟ آیا بهترین راه ایجاد یک تکرارپذیر همینه که ما نوشتیم؟

پارانتز باز:

Generator با Generator Function در جاوااسکریپت

سال 2015، ES6 کلی استاندارد جدید برای JS ارائه کرد. حتی Symbol هم در این ورژن معرفی شد.

یکی از ویژگی های مهم ارائه شده، روش جدید کار با توابع و تکرارشونده ها (iterators) بود. یعنی Generator ها.

یک generator در واقع تایعی است که هر وقت بخوایم میتونیم متوقفش کنیم و دوباره هر وقت نیاز داشتیم از جایی که متوقف شده اجراش کنیم. در واقع Generator یک تابع با قابلیت تکرار هستش.

برای تعریف یک Generator کافیه بعد از کلمه function یک ستاره (*) بزاریم. مانند

function* myGenerator(){ // ...}

و در داخل تابع، هر جایی که بخوایم یک توقف (pause) داشته باشیم، از yield استفاده میکنیم.
فراخوانی هر Generator یک شئ برامون برمیگردونه که متد next جزئی از اون شی هست. و در هر بار فراخوانی متد next، یک Object شامل ویژگی های value و done رو برامون برمیگردونه. که value شامل عنصری است که نشانگر (iterator) بر روی اون قرار داره و done هم نشون میده که iterator به انتهای حلقه رسیده یا نه. (مثل چیزی که ما در ساختار داده خودمون پیاده کردیم.)

به مثال زیر توجه کنید

https://gist.github.com/behnamazimi/5a7dc5bb22ac1184f3b883ca66ff15dc#file-iterables-code-13-js

همونطور که در بالا هم میبینید، نتیجه اجرای myGenerator یک شی با متد next برامون برمیگردونه و برای ایجاد توالی کافیه که متد next رو فراخوانی کنیم.

پس ما میتونیم داخل generator به هر تعداد که لازم داشتیم yield انجام بدیم و در هر مرحله از فراخوانی متد next اجرای کد در یکی از اون yield ها متوقف میشه تا اینکه به آخرین yield برسه.

پارانتز بسته.

بزارید ساختار دادمون رو این بار با generator پیاده کنیم:

https://gist.github.com/behnamazimi/067a4062174177740aece8aedb294632#file-iterables-code-14-js

داخل generator بالا، گفتیم تا زمانی که pointerIndex به عنصر آخر نرسیده yield رو انجام بده. و در هر بار اجرای متد next این generator یک مقدار yield شده به عنوان value در شی `{value: ..., done: false}` برگشت داده میشه.

تا این قسمت چیزی که میخواستم نشون بدم، قابلیت تبدیل کردن یک object به یک iterable بود. ولی مثالی که داشتیم با اینکه ساختار object داشت ولی داده هاش آرایه بود و خیلی راحت میشد روی اون آرایه for...of پیاده کرد چون خودش ذاتا تکرارپذیره. من فقط برای درک بهتر موضوع این مثال رو ترجیح دادم. درسته که در بعضی شرایط به شخصه از این نوع ساختار داده هم استفاده کردم، ولی دوست دارم یک مثال دیگه هم بنویسم که بیشتر با تبدیل object به iterable درگیر باشیم.

فرض کنید اطلاعات کاربر رو در یک object مثل user داریم و میخوایم با قرار دادن object در for...of، هر property رو به صورت آرایه [key, value] برامون برگردونه. کدی که باید بنویسیم میتونه به شکل زیر باشه.

https://gist.github.com/behnamazimi/0da04e8c45f7938e260718e5957caa2b#file-iterables-code-15-js

ما در کد بالا، به ازای هر key از آبجکت user در هر بار حرکت توالی یک آرایه yield کردیم که عنصر اولش کلید ویژگی و اندیس دومش هم مقدار اون ویژگی در شیء ما هستش.

توی این نوشته سعی کردم تکرارپذیری و به همراه اون چند تا موجودیت، تکنیک و ساختار، که شاید درکشون یکم پیچیده به نظر بیاد رو بررسی کنم و با مثال و به صورت عملی توضیح بدم. در کل مباحث مفهومی مثل این شاید در دید اول سخت به نظر بیاد ولی فهمیدن و درکش باعث میشه که برای خیلی از سوالاتتون جواب پیدا کنید و کلا سلسله مراتب و ساختار های مهم جاوا اسکریپت رو کنارش درک کنید.

در هر یک از موارد اگر دیدید توضیح ها کافی نبوده و یا میشد بهتر از این هم به موضوعی پرداخت لطفا برام بنویسید که مطلب رو آپدیت کنم. سوالی هم بود در خدمتم.

امیدوارم براتون مفید واقع بشه :)

در ادامه چند تا از مطالب قبلیم رو هم لینک کردم. باعث افتخاره که بخونید و نظرتون رو بنویسید. سوالی هم بود در خدمتتون هستم.

برای مطالعه نوشته هام منو در توییتر دنبال کنید.