JS Symbol и итераторы
Symbol — уникальный примитивный тип данных, используемый как неповторяемый ключ свойства; итератор — объект с методом
next, реализующий протокол последовательного обхода коллекции.
Зачем нужно
Symbol решает проблему коллизий имён свойств при расширении объектов из чужих библиотек — каждый Symbol уникален, даже если у них одинаковое описание. Итераторы — основа цикла for...of, деструктуризации и spread-оператора: именно они определяют, как объект «разворачивается». Понимание протокола итерирования необходимо для работы с генераторами, Map, Set, кастомными структурами данных.
Где используется
- Уникальные ключи в объектах: избежать случайной перезаписи свойств при миксинах
Symbol.iterator— сделать объект итерируемым дляfor...ofSymbol.toPrimitive,Symbol.hasInstance,Symbol.toStringTag— переопределить встроенное поведение- Метки для приватных или «полупрохожих» свойств (не видны в
for...in, не перечисляютсяJSON.stringify) - Кастомные итерируемые структуры данных: связные списки, деревья, бесконечные последовательности
Основной контент
Symbol
// Каждый Symbol уникален
const s1 = Symbol('id');
const s2 = Symbol('id');
console.log(s1 === s2); // false
// Использование как ключ объекта
const ID = Symbol('id');
const user = {
name: 'Иван',
[ID]: 42
};
console.log(user[ID]); // 42
console.log(user['id']); // undefined
// Не попадает в for...in и Object.keys()
for (const key in user) console.log(key); // только "name"
console.log(Object.keys(user)); // ["name"]
// Но виден через:
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
Встроенные well-known Symbols
// Symbol.toPrimitive — управление приведением типов
const money = {
amount: 100,
currency: 'RUB',
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.amount;
if (hint === 'string') return `${this.amount} ${this.currency}`;
return this.amount;
}
};
console.log(+money); // 100
console.log(`${money}`); // "100 RUB"
// Symbol.toStringTag — переопределить вывод Object.prototype.toString()
class MyCollection {
get [Symbol.toStringTag] { return 'MyCollection'; }
}
console.log(Object.prototype.toString().call(new MyCollection)); // [object MyCollection]
Протокол итерирования
// Итерируемый объект должен иметь метод Symbol.iterator,
// возвращающий итератор с методом next
const range = {
from: 1,
to: 5,
[Symbol.iterator] {
let current = this.from;
const last = this.to;
return {
next {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// Spread и деструктуризация работают благодаря Symbol.iterator
console.log([...range]); // [1, 2, 3, 4, 5]
const [a, b, c] = range;
console.log(a, b, c); // 1 2 3
Кастомная итерируемая структура
class LinkedList {
constructor {
this.head = null;
}
push(value) {
this.head = { value, next: this.head };
return this;
}
[Symbol.iterator] {
let node = this.head;
return {
next {
if (node) {
const value = node.value;
node = node.next();
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const list = new LinkedList();
list.push(3).push(2).push(1);
console.log([...list]); // [1, 2, 3]
Частые ошибки
- Symbol не конвертируется неявно в строку:
'id: ' + Symbol('id')выбросит TypeError — нужно явно вызвать.toString()или.description. - Symbol.for vs Symbol:
Symbol.for('key')возвращает глобальный разделяемый символ — два вызова с одним именем вернут один и тот же объект.Symbolвсегда создаёт новый. - Итератор без возврата
done: trueприведёт к бесконечному циклу вfor...of. - Забыть вернуть
thisиз[Symbol.iterator]: итератор и итерируемый объект могут быть одним объектом, ноnextдолжен быть в возвращаемом объекте.
Связанные темы
Ресурсы
🎓 Источник: Proxy и Symbol в JavaScript
- 📅 2018-11-26 · YouTube
- Тезисы: Symbol — namespace для системных протоколов.
Symbol.iterator,Symbol.asyncIterator,Symbol.toPrimitive,Symbol.hasInstance— это hooks, через которые runtime общается с объектами
🎓 Источник: Итерирование, циклы и итераторы в JavaScript
- 📅 2018-11-05 · YouTube
- Тезисы: Iterator protocol:
Symbol.iteratorвозвращает{ next }, который возвращает{ value, done }. Iterable — то, у чего естьSymbol.iterator. Все встроенные коллекции (Array, Map, Set, String, NodeList) — iterable
🎓 Источник: Архив 2018 - Часть 5 EventEmitter, Symbol, Proxy
- 📅 2020-01-05 · YouTube
- Тезисы: Symbol как private-key обходится через
Object.getOwnPropertySymbols. Полноценная приватность только черезWeakMapили#field(private class field)