Джастин Фуллер, автор материала, перевод которого мы сегодня публикуем, предлагает рассмотреть три новых возможности, появление которых ожидается в JavaScript в обозримом будущем. Сначала он расскажет о процессе развития JS, а после этого представит обзор этих возможностей и покажет примеры их использования. Речь пойдёт об операторах ?., ?? и |>.

О стандарте ECMAScript и развитии JavaScript


Если вы уже знакомы с особенностями деятельности рабочей группы ECMA TC39, с тем, как она осуществляет отбор и обработку предложений о совершенствовании JavaScript, вы вполне можете пропустить этот раздел. Если же вы из тех, кому интересно об этом узнать — вот краткий обзор того, чем занимается TC39.

JavaScript — это реализация стандарта, называемого ECMAScript, который был создан для стандартизации реализаций языка, которые появились в ранние годы существования веб-браузеров.

Существует восемь редакций стандарта ECMAScript и семь релизов (четвёртая редакция стандарта не выходила, после третьей сразу идёт пятая). Разработчики JavaScript-движков приступают к реализации новшеств языка после выхода стандарта. Здесь можно увидеть, что не каждый движок реализует все возможности, при этом некоторым движкам для введения новшеств требуется больше времени, чем другим. Хотя такое положение дел и не идеально, это, всё же, лучше, чем полное отсутствие стандартов.

Предложения


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

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

Мне хотелось бы порекомендовать читателям избегать использования в продакшне новшеств JS, предложения, описывающие которые, находятся на этапе Stage 0. Лучше дождаться перехода их к более стабильным этапам согласования. Цель этой рекомендации заключается в том, чтобы помочь вам избежать проблем в том случае, если предложение будет отвергнуто или окажется очень сильно изменённым.

Система тестирования


Материалы, в которых рассказывают о новых возможностях языков программирования, часто содержат фрагменты кода, вырванные из контекста. Иногда эти возможности используются для создания неких учебных приложений. Однако, ни того, ни другого мы делать здесь не будем. Так как я — большой поклонник TDD, я полагаю, что лучший способ изучения некоей новой технологии заключается в её тестировании.

Мы будем использовать здесь, для освоения описываемых возможностей JS, то, что Джим Ньюкирк называет обучающими тестами. Такие тесты построены не на утверждениях о коде, написанном на некоем языке. Они построены на анализе утверждений, касающихся самого языка. Тот же подход может оказаться полезным и при изучении API сторонних разработчиков, и при освоении любой возможности языка.

Транспиляторы


Если вы не знакомы с транспиляторами, то у вас, возможно, возникнет вопрос о том, как мы собираемся использовать возможности языка, которые пока не реализованы. Тут надо отметить, что JavaScript постоянно развивается, а для реализации в распространённых движках его новых возможностей, интересных программистам, нужно время. В результате в экосистеме JS существует такое понятие, как транспиляторы.

Они позволяют преобразовывать код, написанный на JS с использованием новейших возможностей, которые, например, ещё не включены в стандарты и не реализованы популярными движками, в JS-код, который понимают существующие среды выполнения JavaScript-программ. Это позволят, например, использовать в коде даже предложения уровня Stage 0, а то, что получится после обработки кода транспилятором, можно будет выполнить, например, в современных браузерах или в среде Node.js. Делается это путём преобразования нового кода таким образом, что он, для среды исполнения, выглядит как код, написанный на одной из поддерживаемых ей версий JS.

Одним из самых популярных JavaScript-транспиляторов является Babel, совсем скоро мы поговорим о том, как им пользоваться.

Подготовка рабочей среды


Если вы хотите самостоятельно повторить всё то, о чём мы будем говорить — вы можете это сделать, настроив npm-проект и установив необходимые зависимости. Предполагается, что сейчас у вас уже установлены Node.js и NPM.

Для того чтобы подготовиться к нашим экспериментам, выполните следующую команду, находясь в отведённой для этих экспериментов директории:

npm init -f && npm i ava@1.0.0-beta.3 @babel/preset-env@7.0.0-beta.42 @babel/preset-stage-0@7.0.0-beta.42 @babel/register@7.0.0-beta.42 @babel/polyfill@7.0.0-beta.42 @babel/plugin-transform-runtime@7.0.0-beta.42 @babel/runtime@7.0.0-beta.42 --save-dev

Затем добавьте следующее в файл package.json:

"scripts": {
  "test": "ava"
},
"ava": {    
  "require": [      
    "@babel/register",
    "@babel/polyfill"   
  ]  
}

Далее, создайте файл .babelrc со следующим содержимым:

{  
  "presets": [    
    ["@babel/preset-env", {      
      "targets": {        
        "node": "current"      
      }    
    }],    
    "@babel/preset-stage-0"  
  ],  
  "plugins": [    
    "@babel/plugin-transform-runtime"
  ]
}

Теперь вы готовы к тому, чтобы писать тесты, исследующие новые возможности JS.

1. Оператор ?.


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

const data = {
  user: {
    address: {
      street: 'Pennsylvania Avenue',
    }, 
  },
};

Этот объект описывает пользователя, прошедшего регистрацию. А вот — похожий объект, но в данном случае пользователь, которого он описывает, регистрацию не завершил.

const data = {
  user: {},
};

Если попытаться получить доступ к свойству street объекта address, вложенного в этот «неполный» объект, рассчитывая на то, что он будет выглядеть так же, как объект, содержащий все необходимые данные, можно столкнуться с такой ошибкой:

console.log(data.user.address.street); // Uncaught TypeError: Cannot read property 'street' of undefined

Для того чтобы этого избежать, в текущих условиях необходимо использовать, для обращения к свойству street, такую конструкцию:

const street = data && data.user && data.user.address && data.user.address.street;
console.log(street); // undefined

Я полагаю, что всё это, во-первых, плохо выглядит, во-вторых, это тяжело писать, а в-третьих — подобные конструкции оказываются слишком длинными.

Именно в таких ситуациях как нельзя кстати оказываются опциональные последовательности или опциональные цепочки (optional chaining), представленные оператором, выглядящим как знак вопроса с точкой (?.).

console.log(data.user?.address?.street); // undefined

Выглядит это лучше, да и строить такие конструкции проще. Уверен, вы с этим утверждением согласитесь. Убедившись в полезности этой новой возможности, исследуем её. Напишем тест, поместив код в файл optional-chaining.test.js. Мы, в этом разделе, будем постепенно дополнять этот файл новыми тестами.

import test from 'ava';

const valid = {
  user: {
    address: {
      street: 'main street',
    },
  },
};

function getAddress(data) {
  return data?.user?.address?.street;
}

test('Optional Chaining returns real values', (t) => {
  const result = getAddress(valid);
  t.is(result, 'main street');
});

Этот тест позволяет нам убедиться в том, что оператор ?., в том случае, если объект выглядит так, как ожидается, работает точно так же, как способ доступа к свойствам объекта через точку. Теперь проверим поведение этого оператора в ситуации, когда объект не является тем, чем мы его считаем.

test('Optional chaining returns undefined for nullish properties.', (t) => {
  t.is(getAddress(), undefined);
  t.is(getAddress(null), undefined);
  t.is(getAddress({}), undefined);
});

А вот как опциональные последовательности работают при применении их для доступа к элементам массивов:

const valid = {
  user: {
    address: {
      street: 'main street',
      neighbors: [
        'john doe',
        'jane doe',
      ],
    },
  },
};

function getNeighbor(data, number) {
  return data?.user?.address?.neighbors?.[number];
}

test('Optional chaining works for array properties', (t) => {
  t.is(getNeighbor(valid, 0), 'john doe');
});

test('Optional chaining returns undefined for invalid array properties', (t) => {
  t.is(getNeighbor({}, 0), undefined);
});

Иногда случает так, что нам неизвестно, реализована ли в объекте какая-то функция. Например, такая ситуация распространена при работе с браузерами. Устаревшие браузеры могут не содержать реализаций неких функций. Благодаря оператору ?. мы можем узнать, реализована ли в объекте интересующая нас функция или нет. Вот как это сделать:

const data = {
  user: {
    address: {
      street: 'main street',
      neighbors: [
        'john doe',
        'jane doe',
      ],
    },
    getNeighbors() {
      return data.user.address.neighbors;
    }
  },
};

function getNeighbors(data) {
  return data?.user?.getNeighbors?.();
}
  
test('Optional chaining also works with functions', (t) => {
  const neighbors = getNeighbors(data);
  t.is(neighbors.length, 2);
  t.is(neighbors[0], 'john doe');
});

test('Optional chaining returns undefined if a function does not exist', (t) => {
  const neighbors = getNeighbors({});
  t.is(neighbors, undefined);
});

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

value == null ? value[some expression here]: undefined;

В результате после оператора ?. не будет выполнено ничего в том случае, если значение представлено в виде undefined или null. Взглянуть на это правило в действии можно с помощью следующего теста:

let neighborCount = 0;

function getNextNeighbor(neighbors) {
  return neighbors?.[++neighborCount];
}
  
test('It short circuits expressions', (t) => {
  const neighbors = getNeighbors(data);
  t.is(getNextNeighbor(neighbors), 'jane doe');
  t.is(getNextNeighbor(undefined), undefined);
  t.is(neighborCount, 1);
});

Как видите, опциональные последовательности позволяют уменьшить потребность в конструкциях if, в сторонних библиотеках вроде lodash, и в использовании неуклюжих конструкций, в которых применяется &&.

?Оператор?.. и производительность


Вероятно, вы заметили, что использование опциональных последовательностей означает дополнительную нагрузку на систему. Всё дело в том, что каждый раз, когда используется оператор ?., система вынуждена проводить дополнительные проверки. Злоупотребление оператором ?. может заметно повлиять на производительность программ.

Я посоветовал бы вам использовать эту возможность вместе с некоей системой проверки, которая позволяет анализировать объекты при их получении откуда-либо или при их создании. Это сократит необходимость в использовании конструкции ?. и ограничит её воздействие на производительность.

2. Оператор ??


Вот несколько распространённых операций, с которыми можно встретиться в процессе работы с JavaScript. Обычно они выглядят как единое выражение, смысл которого заключается в следующем:

  1. Проверка значения на undefined и null.
  2. Указание значения, используемого по умолчанию.
  3. Обеспечение того, что появление значений 0, false, и '' не приводит к использованию значения, применяемого по умолчанию.

Вот пример подобного выражения:

value != null ? value : 'default value';

Можно встретить и неграмотно написанный вариант такого выражения:

value || 'default value'

Проблема второго примера заключается в том, что третий пункт вышеприведённого списка здесь не выполняется. Появление тут числа 0, значения false или пустой строки будет распознано как значение false, а это — не то, что нам нужно. Именно поэтому проверку на null и undefined нужно проводить в явном виде:

value != null

Это выражение аналогично такому:

value !== null && value !== undefined

В подобных ситуациях очень кстати окажется новый оператор, называемый «объединение со значением null» (nullish coalescence), который выглядит как два знака вопроса (??). В подобной ситуации теперь можно будет воспользоваться следующей конструкцией:

value ?? 'default value';

Это защищает нас от случайного использования значения по умолчанию при появлении в выражениях значений, распознаваемых как false, но при этом позволяет выявлять значения null и undefined, не прибегая к тернарному оператору и к проверке вида != null.

Теперь, познакомившись с этим оператором, мы можем написать тесты для того, чтобы проверить его в деле. Эти тесты поместим в файл nullish-coalescing.test.js.

import test from 'ava';

test('Nullish coalescing defaults null', (t) => {
  t.is(null ?? 'default', 'default');
});

test('Nullish coalescing defaults undefined', (t) => {
  t.is(undefined ?? 'default', 'default');
});

test('Nullish coalescing defaults void 0', (t) => {
  t.is(void 0 ?? 'default', 'default');
});

test('Nullish coalescing does not default 0', (t) => {
  t.is(0 ?? 'default', 0);
});

test('Nullish coalescing does not default empty strings', (t) => {
  t.is('' ?? 'default', '');
});

test('Nullish coalescing does not default false', (t) => {
  t.is(false ?? 'default', false);
});

Из этих тестов можно понять, что значения по умолчанию используются для null, undefined и void 0 (оно преобразуется в undefined). При этом значения по умолчанию не применяются в тех случаях, когда в выражении появляются значения, воспринимаемые как ложные, вроде 0, пустой строки и false.

3. Оператор |>


В функциональном программировании есть такое понятие, как композиция. Это — действие, представляющее собой объединение в цепочку нескольких вызовов функций. Каждая функция принимает, в качестве входных данных, выходные данные предыдущей функции. Вот пример композиции, подготовленный средствами обычного JavaScript:

function doubleSay (str) {
  return str + ", " + str;
}
function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1);
}
function exclaim (str) {
  return str + '!';
}
let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"

Подобное распространено настолько широко, что средства композиции функций существуют во множестве библиотек, поддерживающих функциональное программирование, например, в таких, как lodash и ramda.

Благодаря новому конвейерному оператору (pipeline operator), который выглядит как комбинация вертикальной черты и знака «больше» (|>), можно отказаться от использования сторонних библиотек и переписать вышеприведённый пример следующим образом:

let result = "hello"
  |> doubleSay
  |> capitalize
  |> exclaim;

result //=> "Hello, hello!"

Цель появления этого оператора заключается в том, чтобы улучшить читаемость цепочек вызовов функций. Кроме того, в будущем, этот оператор найдёт применение в конструкциях частичного применения функций. Сейчас это можно сделать следующим образом:

let result = 1
  |> (_ => Math.max(0, _));

result //=> 1
let result = -5
  |> (_ => Math.max(0, _));

result //=> 0

Разобравшись с основами, напишем тесты, разместив их в файле pipeline-operator.test.js:

import test from 'ava';

function doubleSay (str) {
  return str + ", " + str;
}

function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1);
}

function exclaim (str) {
  return str + '!';
}

test('Simple pipeline usage', (t) => {
  let result = "hello"
    |> doubleSay
    |> capitalize
    |> exclaim;

  t.is(result, 'Hello, hello!');
});

test('Partial application pipeline', (t) => {
  let result = -5
    |> (_ => Math.max(0, _));

  t.is(result, 0);
});

test('Async pipeline', async (t) => {
  const asyncAdd = (number) => Promise.resolve(number + 5);
  const subtractOne = (num1) => num1 - 1;
  const result = 10
    |> asyncAdd
    |> (async (num) => subtractOne(await num));
  
  t.is(await result, 14);
});

Анализируя эти тесты, можно заметить, что при применении в конвейере асинхронной функции, объявленной с использованием ключевого слова async, нужно дождаться появления значения, воспользовавшись ключевым словом await. Дело тут в том, что значение становится объектом Promise. Имеются несколько предложений изменений, направленных на поддержку конструкций вида |> await asyncFunction, но они ещё не реализованы и решение об их дальнейшей судьбе пока не принято.

Итоги


Надеемся, вам понравились ожидаемые возможности JS, которым посвящён этот материал. Вот репозиторий с тестами, которыми мы занимались в этом материале. А вот, для удобства — ссылки на рассмотренные здесь новшества: оператор ?., оператор ??, оператор |>.

Уважаемые читатели! Как вы относитесь к появлению новых возможностей JS, о которых мы рассказали?

Комментарии (37)


  1. argonavtt
    13.04.2018 12:10

    Оператор? = слёзы счастья


    1. Fly3110
      13.04.2018 19:10

      Логичнее бы & смотрелся. Ведь именно этот символ участвует в нынешней реализации


  1. vlreshet
    13.04.2018 13:04

    Оператор |> = какая-то уродливая дичь


    1. Sirion
      13.04.2018 13:06
      +1

      Зачем вы так говорите? Сейчас вас придут минусовать все хаскельщики. Все полтора.


      1. 0xd34df00d
        13.04.2018 17:20
        +1

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


    1. Drag13
      13.04.2018 13:54

      А мне в F# жутко нравится.
      1. Код читается как простая последовательность команд:

       fun pad -> pad.text |>OneTimePad.encrypAndSave |> WritePadView.result |> htmlView
      

      2. Заставляет делать функции еще более узкими и чистыми. Прими — верни, и лучше без сайд эффектов :) Не всегда удается конечно, но все равно приятнее.


    1. salisbury-espinosa
      14.04.2018 14:42
      -1

      юзал в Elixir ( https://hexdocs.pm/elixir/Kernel.html#%7C%3E/2 ), крайне удобный operator для pipes (проброса выхлопа одной функции на вход другой).
      этот паттерн — древний и юзается например в linux для перенаправления output одной прграммы на input другой (https://ru.wikipedia.org/wiki/%D0%A4%D0%B8%D0%BB%D0%BE%D1%81%D0%BE%D1%84%D0%B8%D1%8F_Unix


      Правило композиции: Разрабатывайте программы так, чтобы их можно было соединить с другими программами.

      http://www.linfo.org/pipes.html
      )
      здесь — применяют этот паттерн для конекта функций


      vlreshet


      О себе
      Пишу на JS, PHP, Java и бумажных листиках.

      теперь стало понятно)


  1. dom1n1k
    13.04.2018 13:22

    Первые два очень полезны и желанны, третий — ну как-то не особо. Смотрится инородно в контексте JS.


    1. movl
      13.04.2018 18:04

      В предложении есть еще несколько примеров использования, может они вас убедят:



    1. faiwer
      13.04.2018 21:30
      +5

      Некоторые люди ждут pipe-оператора гораздо гораздо больше ?., ?? и любых других ??????. Это же возможность писать функциональный код слева на право! А изнутри-налево, как сейчас. И это избавления от ада скобочек. И это возможность продолжить цепочки методов методами, которые в цепочке отсутствуют. Да это моя самая желанная вещь со времён async-await! :)


      1. VasilioRuzanni
        14.04.2018 11:14

        Поддерживаю полностью, первые два — приятный синтаксический сахар, а пайплайн — это прямо новая парадигма, по сути, API библиотек будут создаваться с учетом него, это круто.


  1. webdevium
    13.04.2018 13:26
    +1

    --> красиво смотрелся бы для pipelines


    1. mayorovp
      13.04.2018 13:59

      К сожалению, комбинация операторов — и > уже существует в языке…


    1. alsii
      13.04.2018 14:08

      ~>


    1. lam0x86
      13.04.2018 14:22

      a-->b это b(a) или a-- > b


    1. vlreshet
      13.04.2018 14:30
      -1

      ИМХО, любая подобная конструкция всё же будет смотреться несколько инородно для js. Как по мне то идеальным вариантом было бы что-то вроде

      let result = "hello".do(doubleSay, capitalize, exclaim);
      
      Или даже
      let result = doubleSay.pipe(capitalize).pipe(exclaim).pass("hello")
      

      Вместо
      let result = "hello"
          |> doubleSay
          |> capitalize
          |> exclaim;
      
      И транспайлить такое будет довольно просто, и синтаксис однообразный. Почему для нового функционала обязательно надо городить новый синтаксис?


      1. vlreshet
        13.04.2018 15:30

        Человек кинувший минус — мне то пофиг, но ты хоть аргументировал бы, что-ли, за что :) Я же написал что это чисто моё мнение. А то получается «не знаю как, но не так»


        1. mayorovp
          13.04.2018 16:16

          А что если у объекта уже есть метод do?


          1. vlreshet
            13.04.2018 17:21

            Во-первых вариант с ключевым словом do — просто один из вариантов. Во-вторых «коробочные» поля могут быть спокойно перезаписаны. Так что старый код врядли сломался бы, а новый можно было бы писать уже с учётом нового метода. Зато вариант с .do можно реализовать прям сейчас в 5 строчек кода, без дописывания компилятора.


            1. mayorovp
              13.04.2018 17:46

              Вот только применять такую конструкцию можно будет только для своих объектов…


            1. movl
              13.04.2018 17:46
              +1

              Для null и undefined не получится это реализовать.


              Зато вариант с .do можно реализовать прям сейчас в 5 строчек кода, без дописывания компилятора.

              То есть нет никаких ограничений чтобы предложенный вами вариант использовать уже сейчас в своем коде.


              А синтаксис на то он и синтаксис, чтобы делать некоторые вещи опрятнее и проще для понимания, если на ваш вкус это выглядит инородно, то для себя я вижу множество моментов, где с удовольствием бы использовал данный оператор исходя из эстетических предпочтений.


        1. faiwer
          13.04.2018 21:34
          +1

          Как угодно, но не так. Вы взяли уже существующий оператор . и расширили его странным поведением в совершенно базовых условиях, ломая весь существующий js-код. Как вам такое в голову пришло? :) К тому же вы явно невнимательно посмотрели все use-case pipe-оператора.


      1. AngReload
        13.04.2018 19:47
        +1

        Есть уже существующие библиотеки, например Рамда:


        const R = require('ramda')
        
        let resultA = R.pipe(
          doubleSay,
          capitalize,
          exclaim
        )('hello')
        
        let resultB = R.compose(exclaim, capitalize, doubleSay)('hello')


    1. hdfan2
      13.04.2018 14:58
      +2

      Предлагаю оператор o_O. Смотрите, как красиво:

      let result = "hello" o_O doubleSay o_O capitalize o_O exclaim;


      1. Fen1kz
        13.04.2018 16:47
        -1

        А вместо?.. и ?? использовать :/ и х_Х соответственно.

        var result = capitalize(doubleSay(obj && obj.value? value: «hello»))
        V
        const result = obj:/value x_X «hello» o_O doubleSay o_O capitalize


      1. titulusdesiderio
        15.04.2018 12:15

        почему у меня ощущение что на каждом шаге он удивляется всё больше и больше?


  1. Akdmeh
    13.04.2018 15:02

    В принципе, это ведь синтаксический сахар.
    Почему бы и нет? Похожие нововведения местами улучшили синтаксис PHP, и для JS это не особо повлияет на концептуальность языка.


    1. VolCh
      13.04.2018 19:10

      В PHP ?: и ?? чистый сахар, однозначное и простое приведение к известным «паттернам». Тут с?.. сложнее, по-моему.


  1. slavap
    14.04.2018 08:28

    Вместо a.b?.c?.d?.e гораздо читабельнее было бы ?(a.b.c.d.e) или даже nullsafe(a.b.c.d.e)


    1. VolCh
      14.04.2018 08:41

      Или a.b.c.d.e ?? default


      1. XaveScor
        14.04.2018 09:27

        А if(a?.b) {} как?


        1. VolCh
          14.04.2018 09:42

          if (a.b ?? false)


    1. qw1
      15.04.2018 10:27

      Так нельзя выразить, на каких местах точно не null, чтобы компилятор не генерировал лишнюю проверку.

      Вот эти выражения по смыслу разные, как их отличать: a?.b?.c.d.e, или a.b?.c.d.e, или a?.b.c.d?.e


  1. fzn7
    14.04.2018 10:26

    А что за прикол в лямбдах названия аргументов называть как "_"? Дырка на месте переменной улучшает читабельность?


    1. stardust_kid
      14.04.2018 11:36

      Это placeholder, который "выталкивает" аргумент на последнее место при частичном применении аргументов. http://ramdajs.com/docs/#__


  1. mickvav
    14.04.2018 11:28
    -2

    Не знаю, на мой вкус?.. порождает странный код, в котором мы сразу ломимся к непроверенной сущности, вместо того, чтобы где-то раньше проверить, что сущности нет, и уже не пытаться копаться в её потрохах. Может, нужна какая-то простая конструкция, проверяющая, что нужные поля у структуры/объекта есть (а заодно, например, что они проходят какие-то ещё проверки — по регекспам, например)?

    Ну, что-то типа

      match(user, 
         { login : /[A-Za-z]{1,10}/, 
            street : { 
                id : /[0-9]+/, 
                name: /[A-Za-z 0-9]+/ }, 
                password : function(v) { return true; }  
                        }
         } ); 
    


  1. Eldhenn
    14.04.2018 19:44
    +3

    Хех. Когда, по прогнозам, JS догонит Perl? В том смысле, что любая комбинация знаков препинания будет рабочим кодом? :)