ما همیشه از نگارشهایی مثل Destructuring assignment و Spread توی کدهامون استفاده میکنیم؛ اما تا حالا به این فکر کردین که پشت صحنه این نگارشها چطوری کار میکنه؟
ممکنه هنوز تردید داشته باشید که با نگارشهایی که بالاتر مثال زدم آشنا هستید یا نه. پس بریم یکیشون (مثلا Destructuring assignment) رو در عمل ببینیم! به احتمال زیاد قبلا این کد یا مشابه اون رو دیدین:
const [count, setCount] = useState(0);
این کد متعلق به کتابخونه ریاکته و state داخلی یه کامپوننت رو مشخص میکنه. البته خود این کد اینجا اهمیتی نداره و شما میتونید اون رو با هر کد مشابه دیگهای جایگزین کنید.
نکته مهم اینجاست که فانکشن useState یه آرایه به ما برمیگردونه؛ و اگه دقت کنید ما برای نامگذاری هر کدوم از خونههای این آرایهمون به جای استفاده از نگارش پایین، از بالایی استفاده میکنیم که خیلی تمیزتر و خواناتره.
const countStateArray = useState(0) const count = countStateArray[0] const setCount = countStateArray[1]
توی این مثال و نگارشهای مشابه اون مثل Spread راحت میشه متوجه شد که چه اتفاقی داره میافته؛ اما اینکه چطوری این اتفاق میافته چیزیه که قراره بزودی اون رو بفهمیم.
همیشه وقتی صحبت از تکرار یا Iteration در برنامهنویسی میشه یاد حلقهها میافتیم. ما در جاوااسکریپت با استفاده از حلقهها میتونیم یه عملیات رو بارها و به تعداد نیاز تکرار کنیم؛ مثلا کد زیر رو ببینید:
const nums = [1, 2, 3, 4, 5] const rows = Array(2) for (let i = 0; i < rows.length; i++) { rows[i] = nums[i] } console.log(rows) // [1, 2]
توی مثال بالا من تعدادی سطر دارم که باید با عدد پر بشن. البته سطرهای من محدود هستن (۲ تا) و همه اعداد توی اونها جا نمیشن؛ برای همین هم من از اول لیست اعداد شروع میکنم به برداشتن عدد و فقط اون تعدادی رو برمیدارم که بتونم باهاشون سطرهام رو پر کنم.
هرچند این دقیقا همون کاری که Destructuring assignment برامون انجام میده نیست ولی شاید بشه گفت یه ارتباطی بین Iteration و نگارشهایی که تا الان گفتیم وجود داره.
برای اثبات این ارتباط خوبه که اول یکمی توی Iteration عمیقتر بشیم. و با هم چنتا مثال دیگه از Iteration توی جاوااسکریپت رو بررسی کنیم.
نگارش for...of بهمون کمک میکنه توی حلقه بدون نوشتن کدهای اضافه (boilerplate) به تمام مقادیر آرایه دسترسی داشته باشیم. به این مثال نگاه کنید:
const nums = [0, 5, 1] for (const num of nums) { console.log(num) }
توی این مثال اعداد ۰، ۵ و ۱ به ترتیب توی کنسول چاپ میشن. اگه این مثال رو با حلقه for مثال قبل مقایسه کنید میبینید که این نگارش خیلی خواناتره؛ که البته این بهترین قسمتش نیست!
بهترین قسمتش اینه که ما لازم نیست حواسمون باشه که تعداد حلقه بیشتر از طول آرایه نشه یا اینکه مقداری که توی اون حلقه از آرایه باید بگیریم رو به شکل Imperative بنویسیم (مثلا [i]nums)؛ همه این موارد به صورت خودکار توی for...of هندل میشه؛ اما چطوری؟
یادتونه گفتیم یه شباهتی بین for و تمام نگارشهایی که قبلا راجعبهشون صحبت کردیم وجود داره؟ خوب این شباهت توی for...of بیشتر هم هست؛ بخاطر اینکه توی دلش داره از Iteration protocols استفاده میکنه؛ که جادو پشت صحنه بقیه نگارشهایی که راجعبهشون صحبت کردیم هم هست!
پروتکلهای تکرار در اصل برای استاندارد کردن Iteration در جاوااسکریپت معرفی شدن. ما اینجا با ۲ تاشون کار داریم که حتما اسمشون رو از عنوان حدس زدید: تکرارپذیرها (Iterables) و تکرار کنندهها (Iterators)
پروتکل تکرارپذیرها
این پروتکل به تمام تکرارپذیرها اجازه تکرار شدن رو میده! به زبون سادهتر تمام آبجکتها و آرایههایی که ازشون توی حلقهها استفاده میکنیم از یه پروتکلی پیروی میکنن که رفتارشون رو کنترل میکنه.
هر کدوم از تکرارپذیرها یه متدiterator@@
توی خودشون (یا توی پروتوتایپشون) دارن که همونطور که گفتیم مسئول کنترول رفتارشون در Iteration هاست. شما میتونید با استفاده از کلید[Symbol.iterator]
به این متد دسترسی داشته باشید یا اگه دوست دارید اون رو با متد دلخواه خودتون جایگزین کنید.
اگه حال ندارید اینکارو انجام بدید ولی مشتاقید ببینید چی میشه؛ جلوتر خودم یه مثال زدم براتون! :))
پروتکل تکرار کنندهها
پروتکل تکرار کنندهها (Iterators) روش استاندارد تولید دنبالهای از مقادیره! جاوااسکریپت به هر آبجکتی که یدونه متدnext
توی خودش داشته باشه میگه تکرار کننده؛ البته به شرط اینکه اون متد یه سری اصول رو رعایت کنه.
الان دیگه همه دانش لازم برای نتیجه گیری نهایی (اینکه نگارشهایی که راجعبهشون صحبت کردیم و به اینجا رسیدیم، چطور کار میکنن) رو میدونیم؛ اما بیایید قبل از اون، بریم و چیزای جدیدی که یاد گرفتیم رو تو عمل هم ببینیمشون!
همونطور که گفتیم تمام تکرارپذیرها یه متدiterator@@
دارن که توی[Symbol.iterator]
ذخیره شده، و میدونیم که آرایهها یکی از تکرارپذیرهای داخلی جاوااسکریپت هستن؛ تیکه کد زیر رو ببینید:
const nums = [3, 1, 3] console.log(nums[Symbol.iterator]()) // Array Iterator
الان تونستیم با استفاده از کد بالا به متدiterator@@
این آرایه دسترسی داشته باشیم و اونو صدا بزنیم. اینجا جهان جاوااسکریپته و توی این جهان وقتی میتونیم به چیزی دسترسی داشته باشیم یعنی میتونیم تغییرش هم بدیم!
من این تیکه کد پایین رو نوشتم که باعث میشه هر دفعه موقع Iterate روی این آرایه تمام اعداد اون به صورت تصادفی جا به جا میشن! شما هم میتونید اینکارو انجام بدید و بقیه رو ساعتها درگیر دیباگ کدهای خودتون کنید :))
const nums = [1, 2, 3, 4, 5]; nums[Symbol.iterator] = function () { let array = this.sort((a, b) => Math.random() - 0.5) let index = 0 return { next() { return { value: array[index++], done: array.length < index }; }, }; }; console.log(...nums); // 2, 1, 4, 3, 5 console.log(...nums); // 4, 5, 1, 3, 2
الان دیگه به اندازه کافی میدونیم! راجعبه پروتکلهای تکرار صحبت کردیم و همچنین دیدیم که چطوری میشه رفتار پیش فرض اونها رو تغییر داد. با توجه به گفتههای قبلی الان دیگه وقتشه سوال اصلی رو جواب بدیم!
یادتونه تو بخش تکرار کنندهها گفتیم برای اینکه یه تکرار کننده بسازیم کافیه یه آبجکت با متدnext
داشته باشیم؟ و توی مثال چند خط پیش دیدیم که این متدnext
هر بار که صدا زده میشه مقدار فعلی و شرط تموم شدن Iteration رو به ما میده.
این ۲ تا مقداری که متدnext
به ما برمیگردونه، تمام چیزیه که نگارشهایی که راجعبهشون صحبت کردیم (مثل Destructuring assignment و Spread و حتی for...of) بهش نیاز دارن تا درست کار کنن.
این نگارشها با استفاده از روشی که قبلا گفتیم (کلیدSymbol.iterator
) به متدiterator@@
اون تکرارپذیر (مثلا آرایه) دسترسی پیدا میکنن و انقدر متد اونnext
رو صدا میزنن تا مقداری که به عنوان done برمیگردونه true بشه!
و بالاخره جواب سوالمون رو پیدا کردیم! فهمیدیم که نگارشهای اول مقاله چطوری کار میکنن و کنار اون یه مقداری هم توی Iteration جاوااسکریپت عمیقتر شدیم. پیشنهاد میدم بعد از این مقاله مستندات پروتکلهای تکرار رو هم حتما یه نگاهی بندازید.