1 (изменено: Rumata, 2010-10-16 18:56:40)

Тема: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Предлагаю тему для обсуждения. Однажды класс уже вскользь упоминался на форуме - #11. Предлагаемое обсуждение немного перекликается с темой WSH: Сравнение VBScript и JScript

JScript, в отличие от VBScript, не имеет простых языковых средств перебора элементов коллекций. Чтобы решить эту проблему изобретатели из Рэдмонда ввели объект Enumerator

The Enumerator object provides a way to access any member of a collection and behaves similarly to the For...Each statement in VBScript.

Но Enumerator проигрывает в лаконичности For Each: много лишних переменных, громоздкая конструкция:
.
Сокращенный пример JScript из документации

var fso = new ActiveXObject("Scripting.FileSystemObject");

for (var e = new Enumerator(fso.Drives); ! e.atEnd(); e.moveNext()) {
    var drive = e.item();
    ...
}

.
Вариант примера на VBScript

Set fso = CreateObject("Scripting.FileSystemObject")

For Each drive In fso.Drives
    ...
Next

.
Вместо этого предлагаю чуть более лаконичную конструкцию - по сути, "синтаксический сахар", полностью реализованный в духе JavaScript. Встроенный объект Enumerator расширен тремя однотипными статическими методами для обхода коллекций, которые, в зависимости от своей функции, генерируют массив, хэш (объект) или просто перебирают элементы коллекции. Методы по-прежнему используют встроенный объект Enumerator, но реализация полностью сокрыта внутри методов, текущий контекст свободен от избыточных переменных, сохранена лаконичность кода. Сравните с предыдущими примерами:

var fso = new ActiveXObject("Scripting.FileSystemObject");

Enumerator.forItems(fso.Drives, function(drive)
    ...
})

.
Enumerator.forItems принимает два аргумента:
-- коллекцию, элементы которой необходимо перебрать;
-- функцию, которая вызывается для каждого элемента коллекции. Аргументами функции являются текущий элемент коллекции и ссылка на коллекцию. В большинстве ситуацию достаточно указания одного аргумента.

Метод не возвращает результата.

Enumerator.toArray преобразует коллекцию и возвращает массив.
Enumerator.toObject преобразует коллекцию и возвращает объект.

Эти методы избыточны, так как их функциональность полностью реализуема с помощью Enumerate.forItems. Однако они могут быть полезны в силу их интуитивной ясности в контексте использования.

// Преобразовать коллекцию безымянных аргументов командной строки в массив
// Каждый аргумент доступен по его индексу, например, unnamed[0] - первый элемент
var unnamed = Enumerator.toArray(WScript.Arguments.Unnamed, function(value)
{
    return value;
});

// Преобразовать коллецию именованых аргументов командой строки 
// вида /ключ[:значение] в объект вида {"ключ": "значение"} 
// "значение" программно доступно как named["ключ"]
var named = Enumerator.toObject(WScript.Arguments.Named, function(key, collection)
{
    return {
        key: key, 
        value: collection.item(key)
    };
});

.
Далее - исходный код расширения. Полная и актуальная версия доступна по адресу http://code.google.com/p/jsxt/source/br … merator.js

Enumerator.toArray = function(collection, fun, thisp)
{
    if ( typeof fun != "function" ) {
        throw new TypeError();
    }

    var result = [];

    var fc = new Enumerator(collection);
    for ( ; ! fc.atEnd(); fc.moveNext()) {
        var i = fc.item();
        result.push(fun.call(thisp, i, collection));
    }

    return result;
};

Enumerator.toObject = function(collection, fun, thisp)
{
    if ( typeof fun != "function" ) {
        throw new TypeError();
    }

    var result = {};

    var fc = new Enumerator(collection);
    for ( ; ! fc.atEnd(); fc.moveNext()) {
        var i = fc.item();
        var r = fun.call(thisp, i, collection);
        result[r.key] = r.value;
    }

    return result;
};

Enumerator.forItems = function(collection, fun, thisp)
{
    if ( typeof fun != "function" ) {
        throw new TypeError();
    }

    var fc = new Enumerator(collection);
    for ( ; ! fc.atEnd(); fc.moveNext()) {
        var i = fc.item();
        fun.call(thisp, i, collection);
    }
};
( 2 * b ) || ! ( 2 * b )

2

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

а чем прототип не угодил ?

var fso = new ActiveXObject("Scripting.FileSystemObject");
Enumerator.prototype.toArray=function(){x=new Array(i=0);for (;!this.atEnd();this.moveNext()) x[i++]=this.item(); return x};

WScript.Echo(new Enumerator(fso.Drives).toArray());
Я конечно далек от мысли... (с)

3

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

smaharbA пишет:

а чем прототип не угодил ?

Однако!!! Не предполагал, точнее даже знал, что Enumerator имеет прототип.

( 2 * b ) || ! ( 2 * b )

4

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Все встроенные объекты имеют прототип.

5

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Да это так. Но Enumerator - неродной для JavaScript. Поэтому я и не подозревал о возможности модифицировать конструктор и прототип.

Често говоря... Enumerator - кривое порождение программистов Редмонда, "костыль". Было странно, что они не вложили аналогичный функционал в конструкцию for in (врядли подобное будет реализовано в констуркции for each in).

( 2 * b ) || ! ( 2 * b )

6

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

В данном случае прототип - не самое лучшее место - теряется доступ к самой коллекции. А вот сам объект Enumerator... Исправил. Теперь зарекомендованная функциональность из под Enumerate "ушла под крыло" объекта Enumerator.

( 2 * b ) || ! ( 2 * b )

7

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Rumata пишет:

Исправил.

А смысл? В Коллекции — там понятно зачем. А здесь… Нет ссылки — не понятно, где именно исправили — раз! Теряется смысл обсуждения последующих постов за исправленным — два! Лучше бы добавили в конец, а туда добавили ссылку на новый пост.

8 (изменено: Rumata, 2010-10-16 19:00:25)

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Спсибо за замечание. Исправил текст в первом сообщении и код по ссылке, А вот название темы - виноват, забыл. Испраляю повторно. Привожу характер исправлений:

1. объект Enumerate удален
2. методы объекта Enumerate перенесены в объект Enumerator
3. исправлен исходный текст в первом сообщении и по ссылке
4. исправлены текст первого сообщения и название темы для соответствия текущим изменениям

( 2 * b ) || ! ( 2 * b )

9

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Rumata пишет:

...
Но Enumerator проигрывает в лаконичности For Each: много лишних переменных, громоздкая конструкция:
.
Сокращенный пример JScript из документации

var fso = new ActiveXObject("Scripting.FileSystemObject");

for (var e = new Enumerator(fso.Drives); ! e.atEnd(); e.moveNext()) {
    var drive = e.item();
    ...
}

.
Вариант примера на VBScript

Set fso = CreateObject("Scripting.FileSystemObject")

For Each drive In fso.Drives
    ...
Next

Rumata,

интересное решение.
Особенно для меня - с JScript я работаю недавно и его языковые и функциональные возможности я пока не могу ни оценить в полной мере, ни в полной мере использовать.

Позвольте выскажу свое мнение именно как потенциальный пользователь (притом начинающий в JScript).
Ваше утверждение насчет "проигрывает в лаконичности", на мой взгляд спорно - хотя бы по количеству символов стандартного подхода и Вашего решения.
Но вот - опять же исключительно по моему мнению - в "прозрачности", "понятности" стандартный вызов явно впереди.
Просто в силу того, что цикл "следующий до тех пор пока не конец" существует в любом языке.

Если уж говорить о развитии работы с коллекциями, то и в VBS и в JScript есть действительно принципиальное ограничение - движение только в одном направлении (moveNext).
Хотя принципиальных проблем движения movePrevious я не вижу. А вот ведь нет его.

PS Название повеселило -  особенно последнее слово...

10 (изменено: Rumata, 2010-10-21 19:34:45)

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Конечно, отдельная библиотека, дополнительные слова, все это в сумме не будет лаконичнее станартного решения. И Вы правы, что "следующий до конца" понятен каждому. Однако JavaScript имеет свой собственный путь, "путь JavaScript". В языке есть такие непривычные на первый взгляд понятия как пространство имен, замыкание, прототипное наследование, контекст исполнения/вызова.

Многие js-библиотеки реализуют различные расширения стандартных свойств объектов, добавляют свои. Например, стандартный перебор элементов массива:

for (var i = 0; i < arr.length; i++) {
    alert(arr[i]);
}

Эти библиотеки часто делают так:

// расширяя методы стандарных объектов (Firefox реализует или уже реализовал этот метод, МСИЕ - собирается в 8 или 9 версии)
arr.forEach(function(v)
{
    alert(v);
});

// или как в популярной jQuery применяя методы к объектам
$.each(arr, function(v)
{
    alert(v);
});

.
Я понимаю, что многим такое кажется странным, но, повторюсь, это путь JavaScript. К нему привыкаешь и начинаешь мыслить также : ) Абсолютно естественно, что данное решение было сделано именно в таком же ключе. Читая, или перенося некий vbscript-код:

For Each objVar In objColl
...
Next

Я думаю, о том как это будет выглядеть в JScript и ужасался:

for (var fc = new Enumerator(objColl); ! fc.atEnd(); fc.moveNext()) {
    var objVar = fc.item();
...
}

Это код вызывает грусть. Он некрасивый. Опять посмотрите на VBScript. Вот почему я сокрушался раньше что не смогли разработчики сделать аналогичную конструкцию в JScript. Поэтому мне, привыкшему много писать и разбираться в JavaScript, следующий код ближе, яснее и понятнее предыдущего.

Enumerator.forItems(objColl, function(objVar)
{
....
});

P.S.:
Название повеселило -  особенно последнее слово...
А меня-то как оно веселило, пока пытался втиснуть хоть какое-то более менее осмысленное название в поле имени темы. И одна буква все-таки не влезла.

( 2 * b ) || ! ( 2 * b )

11

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Так и не смог я в тот вечер понять...
Приведите пожалуйста один полностью рабочий фрагмент применения .forItems, включая ее определение, например имена дисков показать через WScript.Echo()
Пытался в течение часа применить очевидную конструкцию. Очевидным оказалось лишь то, что чего-то не понял.

12

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Enumerator.forItems = function(collection, fun, thisp)
{
    if ( typeof fun != "function" ) {
        throw new TypeError();
    }

    var fc = new Enumerator(collection);
    for ( ; ! fc.atEnd(); fc.moveNext()) {
        var i = fc.item();
        fun.call(thisp, i, collection);
    }
};

var fso = new ActiveXObject('Scripting.FileSystemObject');
var drives = fso.Drives;

Enumerator.forItems(drives, function(drive)
{
    WScript.Echo(drive);
});
( 2 * b ) || ! ( 2 * b )

13

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Не работает, если определение Enumerator находится ниже его использования.
Ну да, это ведь не полноценная функция и не класс, чтобы быть описанным позже.

14

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Вы издеваетесь? Я привел полноценный рабочий пример! Это полноценная функция, которая суть метод объекта Enumerator, и которая по определению функция-выражение.

Если Вы заинтересованы в javascript почитайте эту статью http://dmitrysoshnikov.com/ecmascript/r … functions/

( 2 * b ) || ! ( 2 * b )

15

Re: JScript: Расширение объекта Enumerator, упрощение работы с коллекциями

Я не издеваюсь. Вы привели полноценный рабочий пример, спасибо!
Просто я сообщил, по какой причине у меня оно оказывается не работало. Мне было забавно, что так и не сообразил. Собственно этому и был посвящен смайлик.
Указанную статью прочел, интересный ECMA-цикл, видимо стоит прочесть весь.