Обработка элементов коллекции/массива обычная и частая операция. В JavaScript есть несколько способов обхода коллекции начиная с простого
У объекта Array есть методы обхода всех элементов
В Firefox есть "заполнитель массива" (Array comprehensions)
Итераторы и Генераторы появились в JavaScript 1.7 (по версии Mozilla) они есть пока в Firefox 2+ (в статье будет упомянут способ как их можно «эмулировать» почти во всех браузерах с костылем) Итераторы и Генераторы вносят механизм, позволяющий управлять поведением
Часто для обхода и обработки элементов массива мы пишем большие конструкции, часто копипастим их части. Задача Генераторов и Итераторов усовершенствовать этот процесс, добавив синтаксический сахар.
Все знают, что в JavaScript можно и так обходить практически любой объект:
Массив
Объект
Строка
Какой-то список
Мы обходим все объекты последовательно и не можем управлять последовательностью. Итераторы имеют похожий интерфейс, но предоставляют возможность управлять элементами:
Итератор — это объект, который знает как получить доступ к элементам коллекции по одному за раз, сохраняя свою ткущую позицию (курсор) в этой последовательности. В JavaScript итератор — это объект, который имеет метод
Итератор может использоваться разными способами: напрямую через вызов метода
Существует несколько способов создания итератора:
После создание итератора объект
Для обхода можно использовать
Один из плюсов
Если функции
Для индекса и значения можно выделить собственные переменные, используя
Этот способ создания итераторов не очень полезный и похож на обычный
Некоторые объекты представляют из себя коллекцию элементов, которые должны итерироваться особым образом. Например:
— Итерация объекта Range должна возвращать числа входящие в множество.
— Листья деревьев можно обходить используя поиск в ширину или поиск в глубину
— Результаты запроса к БД возвращаются в виде объекта, который может итерироваться и возвращать набор данных в одной записи.
— Итератор бесконечной математической последовательности (например, последовательность Фибоначчи) должен возвращать результаты один за другим без создания массива бесконечной длины.
Давайте создадим простой объект Range, хранящий диапазон.
Теперь создадим свой итератор, который возвращает последовательность из этого диапазона. Интерфейс итератора требует, чтобы мы создали метод
Как-то не очень удобно. Чтобы избавиться от конструктора
Достаточно удобно, но много лишнего.
Произвольные итераторы удобная штука, но их создание требует аккуратного программирования и запоминания внутреннего состояния. Генераторы — это более общая альтернатива. Они позволяют определить альтернативный алгоритм, используя одну функцию, которая запоминает свое состояние (подобие конечного автомата).
Генератор это специальный тип функций, которая работает как фабрика по созданию итераторов. Любая функция, содержащая хотя бы один
Внимание: В оригинале генераторы доступны только в Firefox 2+ с переопределенной версией JavaScript
Когда функция генератора вызывается его тело не выполняется. Она работает как конструктор, который создает и возвращает Итератор-Генератор. Каждый вызов метода Итератор-Генератора
Опишем в примере:
Функция-Генератор может быть использована в качестве метода
Получилось раза в 3 короче
С помощью генераторов можно создавать бесконечны последовательности. Разберем пример чисел Фибоначчи:
Функциям-генераторам можно передавать аргументы. Можно прерывать генераторы, используя
Генераторы вычисляют свое следующее значение по запросу. Это позволяет создавать последовательности, которые сложно вычисляются или они бесконечны, как в примере выше.
В дополнении к методу
Вот переписанный Фибоначчи генератор, использующий
Вы можете заставить генератор выбросить исключения, вызвав метод
Если генератор не был запущен (не было вызова метода
Вы можете закрыть генератор, используя метод
Этот метод делает следующее:
— Все
— Если
— Генератор уничтожается
«Заполнитель массива» имеет один значительный недостаток при вызове он заполняет массив и занимает память. Когда массив небольшой то это незаметно, в случае с большими массивами это очень дорого, а в случае с бесконечным количеством элементов «Заполнитель массива» не имеет смысла.
Генераторы выполняют вычисления по требованию. Генераторы-выражения схожи с «Заполнителями массива», но вместо создания массива Генераторы-выражения создают итератор-генераторы, которые выполняются по требованию. Вы можете назвать их короткими генераторами.
Предположим, что у нас есть итератор, который обходит большое количество целых чисел. Мы хотим создать новый итератор, который будет итерировать дубли чисел. Используя «Заполнитель массива» мы создадим целый массив в памяти, содержащий умноженные значения:
Генераторы-выражения создадут новый итератор, который будет умножать числа по требованию:
Генератор-выражение можно передавать в качестве аргумента функции. Дополнительные скобки в этом случае можно упустить:
Дубли чисел, последовательность Финобаччи это очень круто, но как-то далеко от жизни. Посмотрим примеры из жизни:
На странице есть несколько ссылок. Для каждой необходимо получить домен и
Ссылки для теста:
Наш код до Генераторов:
Все естественно и понятно. А если этот цикл нам нужно будет использовать ещё для других проверок? Скопипаcтим код? Генераторы helps ;) Перепишем:
Кода получилось немного больше, но код вырос в ширину(это важный плюс) и теперь мы можем использовать генератор
Есть дерево в котором разбросаны строки. Необходимо вывести их в порядке возрастания:
В коде была одна громоздкая структура
Которую в будущем хотят заменить на:
— Красивого обхода итерируемых объектов: списков (строки в файле, результат запроса к БД, ), рекурсивного обхода деревьев (XML, HTML, Binary, и других произвольных), создание и обход Range-объектов (0..10)
— Обход access логов. Например есть несколько ротированных access-log'ов необходимо собрать информацию об объеме переданных данных
— yield может выполнять роль точки остановки при дебаге
— Можно использовать для разбиения длинного цикла на несколько частей (как по времени так и по количеству обработанных элементов цикла)
— Генератор псевдослучайных чисел или последовательностей
— Основа для Визардов/Мастеров (пошаговый интерфейс)
— Могут использоваться для изменения поведения события (например клика на кнопку)
1. Выполняют вычисления по требованию
2. Не занимают лишнюю память
3. Инкапсулируют процесс получения элемента коллекции
4. Растягивают код
5. Генераторы можно применять как фильры, склеивая в pipeline
Судя по питоновским тестам Генераторы не уступают обычным циклам. Подробнее в презентации "Generator Tricks For Systems Programmers An Introduction" (Слайд 22)
По тесту Generator vs Common Loop генераторы на треть медленнее цикла.
В Firefox 2+ с оверрайдом версии JavaScript можно писать прям сейчас. С остальными все хуже. Существует один хороший проект — Google traceur-compiler. Этот компилятор позволяет обработать код будущих версий ES6+ в код ES3. Но это не панацея, код из примера «Обход дерева» не заработал ни в одном браузере
TC не понимает циклы for..in у генераторов:
Ему нужно
Но такой вид не понимает Firefox. TC требует
В оригинале Генераторы можно использовать только в Firefox, а значит использовать нельзя (но поиграть можно). Даже если допилить
Единственный место где их можно удачно применить — будущий SpiderNode. Итераторы и Генераторы невероятно полезная штука. На клиенте мы будем ими пользоваться лет через 5 (по всем известным причинам). На сервере, надеюсь, в ближайшее время.
1. MDC — Array comprehensions
2. ES Harmony Iterators
3. ES Harmony Generators
4. MDC — MDC Iterators and Generators Guide
5. MDC — New in JavaScript 1.7
6. traceur-compiler LanguageFeatures
JavaScript перенял генераторы и итераторы 1-в-1 из Python, но примеров на JavaScript пока мало. Кину палу ссылок на ресурсы Python:
7. PEP 380, "Syntax for delegating to a sub-generator"
8. PEP 255, "Simple generators"
9. Презентация "Generator Tricks For Systems Programmers An Introduction"
Предложения, пожелания, критика приветствуются!
UPD Добавил тест производительности генераторов Generator vs Common Loop
for(;;)
и for a in b
var divs = document.querySelectorAll('div');
for (var i = 0, c = divs.length; i < c; i++) {
console.log(divs[i].innerHTML);
}
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log(i, obj[i]);
}
У объекта Array есть методы обхода всех элементов
map(), filter()
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map(function (item) {
return item * 2;
});
console.log(doubled);
В Firefox есть "заполнитель массива" (Array comprehensions)
var numbers = [1, 2, 3, 4];
var doubled = [i * 2 for each (i in numbers)];
console.log(doubled); // [2, 4, 6, 8]
Итераторы и Генераторы появились в JavaScript 1.7 (по версии Mozilla) они есть пока в Firefox 2+ (в статье будет упомянут способ как их можно «эмулировать» почти во всех браузерах с костылем) Итераторы и Генераторы вносят механизм, позволяющий управлять поведением
for in
и инкапсулировать процесс получения следующего элемента в списке объектов.Часто для обхода и обработки элементов массива мы пишем большие конструкции, часто копипастим их части. Задача Генераторов и Итераторов усовершенствовать этот процесс, добавив синтаксический сахар.
Итераторы
Все знают, что в JavaScript можно и так обходить практически любой объект:
Массив
var object = [1,2,3];
for (var i in object) {
console.log(i, object[i]);
}
Объект
var object = {a:1, b:2, c:3};
for (var i in object) {
console.log(i, object[i]);
}
Строка
var object = 'abc';
for (var i in object) {
console.log(i, object[i]);
}
Какой-то список
var object = document.querySelectorAll('div');
for (var i in object) {
if (object.hasOwnProperty(i)) {
console.log(i, object[i]);
}
}
Мы обходим все объекты последовательно и не можем управлять последовательностью. Итераторы имеют похожий интерфейс, но предоставляют возможность управлять элементами:
Итератор — это объект, который знает как получить доступ к элементам коллекции по одному за раз, сохраняя свою ткущую позицию (курсор) в этой последовательности. В JavaScript итератор — это объект, который имеет метод
next()
, который возвращает следующий объект в последовательности. Этот метод может вызывать исключение StopIteration
, когда последовательность закончилась. Итератор может использоваться разными способами: напрямую через вызов метода
next()
или используя конструкции for...in
или for each
(только FF).Существует несколько способов создания итератора:
Вызов функции Iterator
var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang); // <<<
После создание итератора объект
it
можно итерировать используя next()
var pair = it.next(); // Врзвращает ["name", "JavaScript"]
pair = it.next(); // Врзвращает ["birthYear", 1995]
pair = it.next(); // Будет выброшено исклюение StopIteration
Для обхода можно использовать
for...in
или for each
. Обход будет остановлен как только будет выброшено исключение StopIteration
:var it = Iterator(lang);
for (var pair in it) {
console.log(pair); // 2 пары [key, value]
}
Один из плюсов
Iterator()
в том, что он обходит только собственные свойства (вспомните пример обхода querySelectorAll
) при обходе вам не нужно проверять object.hasOwnProperty
.Iterator()
может использоваться и для массива:var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs);
for (var pair in it) {
console.log(pair); // [index, language]
}
Если функции
Iterator
передать второй аргумент, то итерироваться будут только индексы: var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs, true);
for (var pair in it) {
console.log(pair); // 0, 1, 2
}
Для индекса и значения можно выделить собственные переменные, используя
let
и деструктивное присваивание(Только FF):var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs);
for (let [i, lang] in it) { // Только FF
console.log(i + ': ' + lang); // напечатает "0: JavaScript" итд.
}
Этот способ создания итераторов не очень полезный и похож на обычный
for in
без Iterator
.Создание своих итераторов
Некоторые объекты представляют из себя коллекцию элементов, которые должны итерироваться особым образом. Например:
— Итерация объекта Range должна возвращать числа входящие в множество.
— Листья деревьев можно обходить используя поиск в ширину или поиск в глубину
— Результаты запроса к БД возвращаются в виде объекта, который может итерироваться и возвращать набор данных в одной записи.
— Итератор бесконечной математической последовательности (например, последовательность Фибоначчи) должен возвращать результаты один за другим без создания массива бесконечной длины.
Давайте создадим простой объект Range, хранящий диапазон.
function Range(low, high){
this.low = low;
this.high = high;
}
Теперь создадим свой итератор, который возвращает последовательность из этого диапазона. Интерфейс итератора требует, чтобы мы создали метод
next()
, который возвратит элемент последовательности и в конце выбросит исключение StopIteration
.function RangeIterator(range){
this.range = range;
this.current = this.range.low;
}
RangeIterator.prototype.next = function(){
if (this.current > this.range.high)
throw StopIteration;
else
return this.current++;
};
var ri = new RangeIterator(new Range(1, 10));
ri.next();
// ...
ri.next(); // StopIteration
Как-то не очень удобно. Чтобы избавиться от конструктора
RangeIterator
будем использовать метод __iterator__
у объекта Range
. Этот метод будет вызван при попытке итерировать элементы объекта Range
этот метод должен возвратить RangeIterator
, включающий логику итератора. Range.prototype.__iterator__ = function(){
return new RangeIterator(this);
};
var range = new Range(3, 5);
for (var i in range) {
console.log(i); // 3, 4, 5
}
Достаточно удобно, но много лишнего.
Генераторы
Произвольные итераторы удобная штука, но их создание требует аккуратного программирования и запоминания внутреннего состояния. Генераторы — это более общая альтернатива. Они позволяют определить альтернативный алгоритм, используя одну функцию, которая запоминает свое состояние (подобие конечного автомата).
Генератор это специальный тип функций, которая работает как фабрика по созданию итераторов. Любая функция, содержащая хотя бы один
yield
становится генератором.Внимание: В оригинале генераторы доступны только в Firefox 2+ с переопределенной версией JavaScript
<script type="application/javascript;version=1.7">
Когда функция генератора вызывается его тело не выполняется. Она работает как конструктор, который создает и возвращает Итератор-Генератор. Каждый вызов метода Итератор-Генератора
next()
будет выполнять тело функции до тех пор пока не попадется yield
, которая возвратит результат. Если функция завершится с return
или тело функции закончится будет выброшено исключение StopIteration
и итератор прекратит свою работу. Опишем в примере:
function simpleGenerator(){
yield "first";
yield "second";
yield "third";
for (var i = 0; i < 3; i++) {
yield i;
}
}
var g = simpleGenerator();
console.log(g.next()); // "first"
console.log(g.next()); // "second"
console.log(g.next()); // "third"
console.log(g.next()); // 0
console.log(g.next()); // 1
console.log(g.next()); // 2
console.log(g.next()); // StopIteration
Функция-Генератор может быть использована в качестве метода
__iterator__
у объекта-итератора. Это позволит значительно уменьшить объем кода. Перепишем пример с Range используя генераторы:function Range(low, high){
this.low = low;
this.high = high;
}
Range.prototype.__iterator__ = function(){
for (var i = this.low; i <= this.high; i++) {
yield i;
}
};
var range = new Range(3, 5);
for (var i in range) {
console.log(i); // 3, 4, 5
}
Получилось раза в 3 короче
С помощью генераторов можно создавать бесконечны последовательности. Разберем пример чисел Фибоначчи:
function fibonacci(){
var fn1 = 1;
var fn2 = 1;
while (1){
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
yield current;
}
}
var sequence = fibonacci();
console.log(sequence.next()); // 1
console.log(sequence.next()); // 1
console.log(sequence.next()); // 2
console.log(sequence.next()); // 3
console.log(sequence.next()); // 5
console.log(sequence.next()); // 8
// ...
Функциям-генераторам можно передавать аргументы. Можно прерывать генераторы, используя
StopIteration
или return
. Перепишем пример с числами Фибоначчи, используя аргумент limit
.function fibonacci(limit){
var fn1 = 1;
var fn2 = 1;
while (1) {
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
if (limit && current > limit) {
return;
}
yield current;
}
}
var sequence = fibonacci(7);
console.log(sequence.next()); // 1
console.log(sequence.next()); // 1
console.log(sequence.next()); // 2
console.log(sequence.next()); // 3
console.log(sequence.next()); // 5
console.log(sequence.next()); // StopIteration
Продвинутые Генераторы
Генераторы вычисляют свое следующее значение по запросу. Это позволяет создавать последовательности, которые сложно вычисляются или они бесконечны, как в примере выше.
В дополнении к методу
next()
, Генератор-Итератор имеет метод send()
он позволяет изменить внутреннее состояние генератора. Значение переданное в send()
будет восприниматься как результат последнего yield
, остановившего генератор. Вы обязаны запустить генератор с помощью next()
перед тем как вызывать send()
. Вот переписанный Фибоначчи генератор, использующий
send()
для рестарта последовательности:function fibonacci(){
var fn1 = 1;
var fn2 = 1;
while (1){
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
var reset = yield current;
if (reset){
fn1 = 1;
fn2 = 1;
}
}
}
var sequence = fibonacci();
print(sequence.next()); // 1
print(sequence.next()); // 1
print(sequence.next()); // 2
print(sequence.next()); // 3
print(sequence.next()); // 5
print(sequence.send(true)); // 1
print(sequence.next()); // 1
print(sequence.next()); // 2
print(sequence.next()); // 3
Вы можете заставить генератор выбросить исключения, вызвав метод
throw()
. Этот метод принимает один аргумент, который и выбросит генератор (throw value;
). Это исключение будет выброшено из текущего остановленного контекста генератора (в том месте где был последний yield
).Если генератор не был запущен (не было вызова метода
next()
), то throw()
вызовет next()
и в месте yield
выбросит исключение. Вы можете закрыть генератор, используя метод
close()
:Этот метод делает следующее:
— Все
finally
блоки будут выполнены— Если
finally
блок выбрасывает исключение отличное от StopIteration
, то исключение будет проброшено в контекст из которого был вызван метод close()
— Генератор уничтожается
Генераторы-Выражения
«Заполнитель массива» имеет один значительный недостаток при вызове он заполняет массив и занимает память. Когда массив небольшой то это незаметно, в случае с большими массивами это очень дорого, а в случае с бесконечным количеством элементов «Заполнитель массива» не имеет смысла.
Генераторы выполняют вычисления по требованию. Генераторы-выражения схожи с «Заполнителями массива», но вместо создания массива Генераторы-выражения создают итератор-генераторы, которые выполняются по требованию. Вы можете назвать их короткими генераторами.
Предположим, что у нас есть итератор, который обходит большое количество целых чисел. Мы хотим создать новый итератор, который будет итерировать дубли чисел. Используя «Заполнитель массива» мы создадим целый массив в памяти, содержащий умноженные значения:
var doubles = [i * 2 for (i in it)];
Генераторы-выражения создадут новый итератор, который будет умножать числа по требованию:
var it2 = (i * 2 for (i in it));
print(it2.next()); // Первое удвоенное значение
print(it2.next()); // Второе удвоенное значение
Генератор-выражение можно передавать в качестве аргумента функции. Дополнительные скобки в этом случае можно упустить:
var result = doSomething(i * 2 for (i in it));
Примеры
Дубли чисел, последовательность Финобаччи это очень круто, но как-то далеко от жизни. Посмотрим примеры из жизни:
1. Обход DOM дерева
На странице есть несколько ссылок. Для каждой необходимо получить домен и
innerHTML
и если innerHTML
совпадает с доменом вывести домен в консоль. Ссылки для теста:
<a href="http://ya.ru/">Я ру</a>
<a href="http://google.ru/">Гугл</a>
<a href="http://habrahabr.ru/">Хабр</a>
<a href="http://twitter.com/">twitter.com</a>
Наш код до Генераторов:
var aList = document.querySelectorAll('a');
for (var i = 0, c = aList.length, a, content, domain; i < c; i++) {
a = aList[i];
content = a.innerHTML;
domain = a.getAttribute('href').replace('http://', '').split('/').shift();
if (content === domain) {
console.log(domain);
}
}
Все естественно и понятно. А если этот цикл нам нужно будет использовать ещё для других проверок? Скопипаcтим код? Генераторы helps ;) Перепишем:
// Не забываем про type="application/javascript;version=1.7"
var aDomainContentGenerator = function () {
var aList = document.querySelectorAll('a');
for (var i = 0, c = aList.length, a, content, domain; i < c; i++) {
a = aList[i];
content = a.innerHTML;
domain = a.getAttribute('href').replace('http://', '').split('/').shift();
yield {domain: domain, content: content};
}
};
var ancors = aDomainContentGenerator();
// Используем Выражения-Генераторы для выделения необходимых нам объектов
// Если использовать for c if, то необходимы доп. скобки, иначе ничего не получится
var ancorsWithSameContentAndDomain = (item for (item in ancors) if (item.content === item.domain));
for (item in ancorsWithSameContentAndDomain) {
console.log(item.domain);
}
Кода получилось немного больше, но код вырос в ширину(это важный плюс) и теперь мы можем использовать генератор
aDomainContentGenerator()
и для других целей.2. Обход дерева
Есть дерево в котором разбросаны строки. Необходимо вывести их в порядке возрастания:
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// Рекурсивный генератор для обхода дерева
function inorder(t) {
if (t) {
for (var item in inorder(t.left)) {
yield item;
}
yield t.label;
for (var item in inorder(t.right)) {
yield item;
}
}
}
// Функция создания дерева
function make(array) {
// Элемент дерева:
if (array.length == 1) return new Tree(null, array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
var tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// Итерируем
for (var node in inorder(tree)) {
console.log(node); // a, b, c, d, ...
}
В коде была одна громоздкая структура
for (var item in inorder(t.right)) {
yield item;
}
Которую в будущем хотят заменить на:
yield for inorder(t.right);
Генераторы можно применить для
— Красивого обхода итерируемых объектов: списков (строки в файле, результат запроса к БД, ), рекурсивного обхода деревьев (XML, HTML, Binary, и других произвольных), создание и обход Range-объектов (0..10)
— Обход access логов. Например есть несколько ротированных access-log'ов необходимо собрать информацию об объеме переданных данных
— yield может выполнять роль точки остановки при дебаге
— Можно использовать для разбиения длинного цикла на несколько частей (как по времени так и по количеству обработанных элементов цикла)
— Генератор псевдослучайных чисел или последовательностей
— Основа для Визардов/Мастеров (пошаговый интерфейс)
— Могут использоваться для изменения поведения события (например клика на кнопку)
Преимущества генераторов
1. Выполняют вычисления по требованию
2. Не занимают лишнюю память
3. Инкапсулируют процесс получения элемента коллекции
4. Растягивают код
5. Генераторы можно применять как фильры, склеивая в pipeline
Производительность
Судя по питоновским тестам Генераторы не уступают обычным циклам. Подробнее в презентации "Generator Tricks For Systems Programmers An Introduction" (Слайд 22)
По тесту Generator vs Common Loop генераторы на треть медленнее цикла.
Когда это можно будет использовать?
В Firefox 2+ с оверрайдом версии JavaScript можно писать прям сейчас. С остальными все хуже. Существует один хороший проект — Google traceur-compiler. Этот компилятор позволяет обработать код будущих версий ES6+ в код ES3. Но это не панацея, код из примера «Обход дерева» не заработал ни в одном браузере
TC не понимает циклы for..in у генераторов:
for (var node in inorder(tree)) {
console.log(node); // a, b, c, d, ...
}
Ему нужно
for (let node : inorder(tree)) {
console.log(node); // a, b, c, d, ...
}
Но такой вид не понимает Firefox. TC требует
Function#bind
, Object.defineProperty
поэтому скомпилированная версия работает только в браузере создателя TC — Google Chrome :). Скомпилированный код генератора получается монстрообразным с применением конечного автомата и плясок вокруг try catch
и прочих извратов. Получается такой «код»:function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
function inorder(t) {
var $that = this;
return { __traceurIterator__: function() {
var $state = 20;
var $storedException;
var $finallyFallThrough;
var $__1;
var $__2;
var $__3;
var $__4;
var $result = { moveNext:(function() {
while(true) try {
switch($state) {
case 20:
if(t) {
$state = 7;
break;
} else {
$state = 15;
break;
}
case 7:
$__2 = inorder(t.left).__traceurIterator__();
$state = 8;
break;
case 8:
if($__2.moveNext()) {
$state = 2;
break;
} else {
$state = 5;
$finallyFallThrough = 4;
break;
}
case 2:
$__1 = $__2.current;
$state = 3;
break;
case 3:
$result.current = $__1;
$state = 8;
return true;
case 5:
{
if($__2.close) $__2.close();
}
$state = 6;
break;
case 4:
$result.current = t.label;
$state = 10;
return true;
case 10:
$__4 = inorder(t.right).__traceurIterator__();
$state = 19;
break;
case 19:
if($__4.moveNext()) {
$state = 13;
break;
} else {
$state = 16;
$finallyFallThrough = 15;
break;
}
case 13:
$__3 = $__4.current;
$state = 14;
break;
case 14:
$result.current = $__3;
$state = 19;
return true;
case 16:
{
if($__4.close) $__4.close();
}
$state = 17;
break;
case 6:
$state = $finallyFallThrough;
break;
case 17:
$state = $finallyFallThrough;
break;
case 15:
$state = 22;
case 22:
return false;
case 21:
throw $storedException;
default:
throw "traceur compiler bug: invalid state in state machine" + $state;
}
} catch($caughtException) {
$storedException = $caughtException;
switch($state) {
case 8:
$state = 5;
$finallyFallThrough = 21;
break;
case 2:
$state = 5;
$finallyFallThrough = 21;
break;
case 3:
$state = 5;
$finallyFallThrough = 21;
break;
case 19:
$state = 16;
$finallyFallThrough = 21;
break;
case 13:
$state = 16;
$finallyFallThrough = 21;
break;
case 14:
$state = 16;
$finallyFallThrough = 21;
break;
default:
throw $storedException;
}
}
}).bind($that) };
return $result;
} };
}
function make(array) {
if(array.length == 1) return new Tree(null, array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
var tree = make([[['a'], 'b',['c']], 'd',[['e'], 'f',['g']]]);
{
var $__0 = inorder(tree).__traceurIterator__();
try {
while($__0.moveNext()) {
try {
throw undefined;
} catch(node) {
node = $__0.current;
{
console.log(node);
}
}
}
} finally {
if($__0.close) $__0.close();
}
}
Код на половину статьи — оставил, чтобы показать, что с ним все плохо...Заключение
В оригинале Генераторы можно использовать только в Firefox, а значит использовать нельзя (но поиграть можно). Даже если допилить
Function#bind
и Object.defineProperty
, то в любом случае TC превращает код в тормозного монстра и поэтому его применение сомнительное.Единственный место где их можно удачно применить — будущий SpiderNode. Итераторы и Генераторы невероятно полезная штука. На клиенте мы будем ими пользоваться лет через 5 (по всем известным причинам). На сервере, надеюсь, в ближайшее время.
Почитать
1. MDC — Array comprehensions
2. ES Harmony Iterators
3. ES Harmony Generators
4. MDC — MDC Iterators and Generators Guide
5. MDC — New in JavaScript 1.7
6. traceur-compiler LanguageFeatures
JavaScript перенял генераторы и итераторы 1-в-1 из Python, но примеров на JavaScript пока мало. Кину палу ссылок на ресурсы Python:
7. PEP 380, "Syntax for delegating to a sub-generator"
8. PEP 255, "Simple generators"
9. Презентация "Generator Tricks For Systems Programmers An Introduction"
Предложения, пожелания, критика приветствуются!
UPD Добавил тест производительности генераторов Generator vs Common Loop