در مطلب Iterators به بررسی حلقههای for of پرداختیم. اما سؤال مهم اینجا است که for of چگونه یک iterator را پیدا میکند و چه چیزی سبب میشود تا بتواند این پیمایش را انجام دهد؟ پاسخ به این سؤال نیاز به آشنایی با مفهوم جدیدی در
ES 6 به نام Symbols دارد.
Symbol یک primitive data type جدید در ES 6 است؛ دقیقا مانند اعداد، Boolean، رشتهها و امثال آنها. دو نکتهی مهم در مورد Symbols وجود دارد:
الف) منحصربفرد و immutable (غیرقابل تغییر) هستند.
ب) میتوان از آنها به عنوان کلیدهایی جهت افزودن خواص جدید به اشیاء استفاده کرد.
ایجاد یک Symbol باید بدون استفاده از کلمهی new انجام شود (چون یک primitive data type است):
همچنین در اینجا یک توضیح را نیز میتوان ذکر کرد:
let s1 = Symbol("some description");
سمبل ایجاد شده، منحصربفرد بوده و غیرقابل تغییر است. همین منحصربفرد بودن آن سبب شدهاست که در لایههای زیرین ES 6 از آن برای ساخت کلیدهای خواص اشیاء استفاده شود:
let firstName = Symbol();
let person = {
lastName: "Vahid",
[firstName]: "N",
};
// person.lastName = "Vahid"
// person[firstName] = "N"
در این مثال ابتدا یک سمبل جدید ایجاد شده و سپس از این سمبل به عنوان کلیدی منحصربفرد، جهت تعریف یک خاصیت جدید کمک گرفته شدهاست.
در ES 5 (نگارش فعلی جاوا اسکریپت)، کتابخانههای مختلف از time stamp و یا اعداد اتفاقی برای شبیه سازی چنین قابلیتی استفاده میکنند اما در ES 6 یک راه حل استاندارد به نام Symbols برای این مساله ارائه شدهاست.
چند نکته
- زمانیکه خاصیتی با کلیدی از نوع Symbol تعریف میشود، دیگر در حلقههای for in قدیمی ظاهر نخواهد شد.
let names = [];
for(var p in person) {
names.push(p);
}
- همچنین این خواص سمبلی، توسط Object.getOwnPropertyNames نیز قابل دسترسی و یافت شدن نیستند. به عبارتی با امکانات ES 5 نمیتوان آنها را مشاهده کرد.
سؤال: ES 6 چگونه از Symbols جهت تعریف Iterators استفاده میکند؟
مطابق استاندارد ES 6 اگر متد خاصی با نام iterator@@ در شیءایی ظاهر شود، این شیء قابل پیمایش بوده و به عنوان منبع حلقهی for of قابل استفادهاست.
خوب، اکنون چگونه میتوان بررسی کرد که آیا شیءایی دارای متد ویژهی iterator@@ است؟ برای این منظور باید بررسی کرد که آیا این شیء دارای عضو Symbol.iterator هست یا خیر؟ خاصیت iterator متصل به متد Symbol، یکی از سمبلهای پیش فرض
ES 6 است.
برای مثال آرایهی ذیل را درنظر بگیرید:
برای اینکه بررسی کنیم آیا قابل پیمایش هست یا خیر، میتوان نوشت:
numbers[Symbol.iterator];
همانطور که در تصویر مشاهده میکنید، آرایه و یا رشتهی تعریف شده، دارای Iterator هستند؛ اما عدد تعریف شده، خیر.
و یا اگر بخواهیم همان
مثال while دار مطلب بررسی Iterators را با Symbol.iterator بازسازی کنیم، به مثال زیر خواهیم رسید:
var numbersIterator = numbers[Symbol.iterator]();
numbersIterator.next();
// Result: Object {value: 1, done: false}
numbersIterator.next();
// Result: Object {value: 2, done: false}
numbersIterator.next();
// Result: Object {value: 3, done: false}
numbersIterator.next();
// Result: Object {value: undefined, done: true}
کاری که در اینجا انجام شده، دقیقا عملیاتی است که توسط حلقهی for of در پشت صحنه انجام میشود. ابتدا بررسی میکند که آیا خاصیت Symbol.iterator در دسترس است یا خیر؟ اگر بله، متد next آنرا تا زمان true شدن خاصیت done بازگشتی، فراخوانی میکند.
ایجاد یک Iterator سفارشی با استفاده از Symbol.iterator
در این مثال قصد داریم یک پیمایشگر سفارشی را بر روی یک رشتهی دریافتی، ایجاد کنیم. ابتدا ایجاد سازندهی شیء:
function Words(str) {
this._str = str;
}
و سپس بدنهی Iterator:
Words.prototype[Symbol.iterator] = function() {
var re = /\S+/g;
var str = this._str;
return {
next: function() {
var match = re.exec(str);
if (match) {
return {value: match[0], done: false};
}
return {value: undefined, done: true};
}
}
};
در اینجا شیءایی بازگشت داده میشود که دارای متد next است و هر بار {value: nextWordInTheString, done: false} را بازگشت میدهد تا دیگر کلمهای در رشته باقی نماند. نمونهای از نحوهی استفادهی از آن نیز به صورت زیر است:
var helloWorld = new Words("Hello world");
for (var word of helloWorld) {
console.log(word);
}
// Result: "Hello"
// Result: "world"