JavaScript 1.8 предоставляет огромное количество вкусного синтаксического сахара, в основном любителями функциональщины. Но очень мало разработчиков знает об этой красоте. Конечно, к сожалению, все эти вкусности не поддерживает даже Chrome (что уж говорить об IE?), а только Firefox 3+, но JavaScript-разработчик просто обязан знать обо всех этих новинках.
Наиболее полную информацию можно найти в статьях на MDN:
А я перевела небольшую, но интересную статью Джона Ресига (автора jQuery), который раскрывает в ней некоторые из новых фич: Expression Closures, Generator Expressions, __iterator__, Array Reduce и кое-что ещё:
Хотя статья написана в 2007 году, я не нашла её перевода на русский язык и вообще достаточно информации по этой теме на русском языке. Весь код в примерах отлично работает в Firefox 3+.
Если вы не следили за прогрессом в разработке JavaScript 1.8, я предлагаю вашему вниманию быстрый обзор того, что уже есть в ночной сборке Firefox 3.
Пока что в последних версиях добавилось три основные возможности, и ожидается еще несколько. Основной задачей этой “легкой” версии является приближение текущей реализации JavaScript к желаемой спецификации JavaScript 2.
Лексические замыкания недавно добавлены в транк, и они наверняка будут интересны любителям функционального программирования. Фактически, это просто изящная запись для написания простых функций, которая делает язык подобным типичной лямбда-нотации.
Давайте глянем на синтаксис лямбда-функций в разных языках:
Python:
Smalltalk:
JavaScript 1.8:
JavaScript 1.7 и старше:
Пожалуй, мой любимый пример — это установка обработчика событий:
Или в сочетании этой нотации и некоторых функций применяемых для массивов из JavaScript 1.6:
Это даст вам элегантный JS/DOM код.
Это ещё один пример из новенького. Он посложнее предыдущего, так как здесь охвачено несколько понятий. Конкретно пример требует знаний большинства особенностей JavaScript 1.7 — особенно итераторов, генераторов и генераторов массивов. Эта особенность базируется на выражениях-генераторах, позаимствованных из Python.
В тикете, отслеживающем данную возможность, Брэндан запостил элегантный и функциональный решатель Судоку, написаный с использованием нового синтаксиса, предлагаемого этим дополнением. Эта демо-версия базируется на аналогичной, написанной на Python, которая демонстрирует применение выражений-генераторов.
Для лучшего понимая значения этой функции, давайте посмотрим на пример кода на JavaScript 1.8, взятого из решателя Судоку.
Это выражение опирается на функцию dict(), которая берёт матрицу размером 2xN и конвертирует её в пару ключ/значение. Вот её код:
Давайте рассмотрим каждый элемент этого выражения для лучшего понимания того, что именно происходит.
Первая часть выражения — пример генерации массивов из JavaScript 1.7. А именно, мы проходим каждый элемент списка
и создаем массив с индексами, которые содержат s.
Вторая часть выражения — другой пример генерации массива. На первый взгляд — это очередная возможность JavaScript 1.7 — деструктивное присваивание (destructuring assignment), но это не так.
Деструктивное присваивание возникает тогда, когда вы присваиваете новое значение старому элементу массива — а здесь мы добавляем это значение в новый массив. Новые двумерные массивы впоследствии будут переданы в функцию dict.
Вот где скрыта магия. В JavaScript 1.7 мы можем вызвать функцию dict() так:
Обратите внимание на явное использование генерации списков. Проблема с дополнительной генерацией заключена в том, что она должна быть выполнена полностью для построения целого массива (который конвертируется в словарь/хэш). Впрочем, отсутствие еще одного [...] и определяет выражение-генератор. Это делает строчку на JavaScript 1.8 эквивалентной следующей строчке на JavaScript 1.7:
Здесь вы видите, что выражение-генератор создает массив лениво — то есть, значения не будут сгенерированы до того момента, как они будут необходимы в функции dict() (в результате мы получаем меньше операций и лучшую производительность)
Вот другой пример выражения-генератора:
Конечно, функция val_iter() может быть построена и с помощью JavaScript 1.7, с использованием yield:
Выражения-генераторы будут особенно полезны в коде, жадном до памяти и CPU — например, в том же решателе Судоку, поскольку теперь есть возможность получать результаты решений только тогда, когда они нужны — а не расчитывать их заранее.
Между прочим, я недавно уже разбирался с итераторами и генераторами — и мне не хватало возможности простой итерации по набору чисел (например, 0-9). Чуть поработав напильником, мы можем добавить такую возможность в язык:
Может и боян, но я пришёл в дичайший восторг от этого.
Последнее, что стоит разобрать — новые методы Array.reduce/Array.prototype.reduce с JavaScript 1.6 Array Extras.
Вы можете применить reduce к массиву таким образом:
с функцией это будет выглядеть так:
Аргумент «lastValue» — результат выполнения коллбэка функции reduce. При первом вызове lastValue будет равно первому элементу списка или значению initial, если вы его передали, а значение curValue — следующему элементу списка
Таким образом, если вы хотите найти суму чисел от 0 до 99, вы можете сделать это следующим образом (используя JavaScript 1.8 и вышеупомянутый числовой итератор)
Изящно, не так ли?
Вы также можете применять reduce для таких вещей, как слияние множества узлов DOM в один массив:
Всё, что я упомянул выше работает в последних ночных сборках Firefox 3, так что если вы хотите попробовать выполнить что-то из вышепоказанного, просто сделайте следующее:
1. Скачайте ночную сборку Firefox 3
2. Создайте страницу, которая содержит следующий тэг (с атрибутом ‘version=1.8’, который только что был добавлен):
И это всё, что нужно – наслаждайтесь!
Наиболее полную информацию можно найти в статьях на MDN:
А я перевела небольшую, но интересную статью Джона Ресига (автора jQuery), который раскрывает в ней некоторые из новых фич: Expression Closures, Generator Expressions, __iterator__, Array Reduce и кое-что ещё:
// Останавливаем выполнение события по-умолчанию
document.addEventListener("click", function() false, true);
// Выводим три сообщения
for ( let i in 3 ) alert( i );
// Создаем массив из 100 элементов, заполненный нулями
[ 0 for ( i in 100 ) ];
// Создаем единичную матрицу 10*10
[[ i == j ? 1 : 0 for ( i in 10 ) ] for ( j in 10 )];
Хотя статья написана в 2007 году, я не нашла её перевода на русский язык и вообще достаточно информации по этой теме на русском языке. Весь код в примерах отлично работает в Firefox 3+.
Прогресс в JavaScript 1.8
Если вы не следили за прогрессом в разработке JavaScript 1.8, я предлагаю вашему вниманию быстрый обзор того, что уже есть в ночной сборке Firefox 3.
Пока что в последних версиях добавилось три основные возможности, и ожидается еще несколько. Основной задачей этой “легкой” версии является приближение текущей реализации JavaScript к желаемой спецификации JavaScript 2.
Лексические замыкания (Expression Closures)
Лексические замыкания недавно добавлены в транк, и они наверняка будут интересны любителям функционального программирования. Фактически, это просто изящная запись для написания простых функций, которая делает язык подобным типичной лямбда-нотации.
Давайте глянем на синтаксис лямбда-функций в разных языках:
Python:
lambda x: x * x
Smalltalk:
[ :x | x * x ]
JavaScript 1.8:
function(x) x * x
JavaScript 1.7 и старше:
function(x) { return x * x; }
Пожалуй, мой любимый пример — это установка обработчика событий:
document.addEventListener("click", function() false, true);
Или в сочетании этой нотации и некоторых функций применяемых для массивов из JavaScript 1.6:
elems.some(function(elem) elem.type == "text");
Это даст вам элегантный JS/DOM код.
Выражения-генераторы (Generator Expressions)
Это ещё один пример из новенького. Он посложнее предыдущего, так как здесь охвачено несколько понятий. Конкретно пример требует знаний большинства особенностей JavaScript 1.7 — особенно итераторов, генераторов и генераторов массивов. Эта особенность базируется на выражениях-генераторах, позаимствованных из Python.
В тикете, отслеживающем данную возможность, Брэндан запостил элегантный и функциональный решатель Судоку, написаный с использованием нового синтаксиса, предлагаемого этим дополнением. Эта демо-версия базируется на аналогичной, написанной на Python, которая демонстрирует применение выражений-генераторов.
Для лучшего понимая значения этой функции, давайте посмотрим на пример кода на JavaScript 1.8, взятого из решателя Судоку.
dict([s, [u for (u in unitlist) if (u.contains(s))]] for (s in squares))
Это выражение опирается на функцию dict(), которая берёт матрицу размером 2xN и конвертирует её в пару ключ/значение. Вот её код:
function dict(A) {
let d = {}
for (let e in A)
d[e[0]] = e[1]
return d
}
Давайте рассмотрим каждый элемент этого выражения для лучшего понимания того, что именно происходит.
[u for (u in unitlist) if (u.contains(s))]
Первая часть выражения — пример генерации массивов из JavaScript 1.7. А именно, мы проходим каждый элемент списка
и создаем массив с индексами, которые содержат s.
[s, ...] for (s in squares)
Вторая часть выражения — другой пример генерации массива. На первый взгляд — это очередная возможность JavaScript 1.7 — деструктивное присваивание (destructuring assignment), но это не так.
Деструктивное присваивание возникает тогда, когда вы присваиваете новое значение старому элементу массива — а здесь мы добавляем это значение в новый массив. Новые двумерные массивы впоследствии будут переданы в функцию dict.
dict([s, ...] for (s in squares))
Вот где скрыта магия. В JavaScript 1.7 мы можем вызвать функцию dict() так:
dict([[s, ...] for (s in squares)])
Обратите внимание на явное использование генерации списков. Проблема с дополнительной генерацией заключена в том, что она должна быть выполнена полностью для построения целого массива (который конвертируется в словарь/хэш). Впрочем, отсутствие еще одного [...] и определяет выражение-генератор. Это делает строчку на JavaScript 1.8 эквивалентной следующей строчке на JavaScript 1.7:
dict((function(){ for (s in squares) yield [s, ...] ; })())
Здесь вы видите, что выражение-генератор создает массив лениво — то есть, значения не будут сгенерированы до того момента, как они будут необходимы в функции dict() (в результате мы получаем меньше операций и лучшую производительность)
Вот другой пример выражения-генератора:
// Создает генератор, проходящий по массиву значений объекта
function val_iter(obj) {
return (obj[x] for (x in obj));
}
// Итерирует по ключам объекта
for ( let key in obj ) { ... }
// Итерирует по значениям объектаа
for ( let value in val_iter(obj) ) { ... }
Конечно, функция val_iter() может быть построена и с помощью JavaScript 1.7, с использованием yield:
function val_iter(obj) {
for (let x in obj)
yield obj[x];
}
Выражения-генераторы будут особенно полезны в коде, жадном до памяти и CPU — например, в том же решателе Судоку, поскольку теперь есть возможность получать результаты решений только тогда, когда они нужны — а не расчитывать их заранее.
Развлекаемся с итераторами
Между прочим, я недавно уже разбирался с итераторами и генераторами — и мне не хватало возможности простой итерации по набору чисел (например, 0-9). Чуть поработав напильником, мы можем добавить такую возможность в язык:
// Добавляем итератор ко всем числам
Number.prototype.__iterator__ = function() {
for ( let i = 0; i < this; i++ )
yield i;
};
// Выводим три сообщения
for ( let i in 3 ) alert( i );
// Создаем массив из 100 элементов, заполненный нулями
[ 0 for ( i in 100 ) ]
// Создаем единичную матрицу 10*10
[[ i == j ? 1 : 0 for ( i in 10 ) ] for ( j in 10 )]
Может и боян, но я пришёл в дичайший восторг от этого.
Array Reduce
Последнее, что стоит разобрать — новые методы Array.reduce/Array.prototype.reduce с JavaScript 1.6 Array Extras.
Вы можете применить reduce к массиву таким образом:
someArray.reduce( fn [, initial] );
с функцией это будет выглядеть так:
someArray.reduce(function(lastValue, curValue){
return lastValue + curValue;
});
Аргумент «lastValue» — результат выполнения коллбэка функции reduce. При первом вызове lastValue будет равно первому элементу списка или значению initial, если вы его передали, а значение curValue — следующему элементу списка
Таким образом, если вы хотите найти суму чисел от 0 до 99, вы можете сделать это следующим образом (используя JavaScript 1.8 и вышеупомянутый числовой итератор)
[x for ( x in 100 )].reduce(function(a,b) a+b);
Изящно, не так ли?
Вы также можете применять reduce для таких вещей, как слияние множества узлов DOM в один массив:
nodes.reduce(function(a,b) a.concat(b.childNodes), []);
Попробуйте сами!
Всё, что я упомянул выше работает в последних ночных сборках Firefox 3, так что если вы хотите попробовать выполнить что-то из вышепоказанного, просто сделайте следующее:
1. Скачайте ночную сборку Firefox 3
2. Создайте страницу, которая содержит следующий тэг (с атрибутом ‘version=1.8’, который только что был добавлен):
<script type="application/javascript;version=1.8"> ... your code ...</script>
И это всё, что нужно – наслаждайтесь!