JS

Null, операторы сравнения и все, все, все

При всей той напускной строгости и минимализме, которыми блещет Javascript, он не дотягивает до уровня строгости и логичности Python, при примерно таком же уровне минимализма. Зачем, например, такое количество разных значений-аналогов хрестоматийного false? Интерпретация false в вычисляемых выражениях это сплошная головная боль — проблемы приведения «пустых» значений есть в самом языке, это раз. Во-вторых львиная доля времени проводится во взаимодействием со встроенными в язык или браузер объектами, и что возвращает каждый из них можно только запомнить. В-третьих, есть еще написанные неведомо кем библиотеки.

Что такое «пустые значения»? Это собирательное название для всех тех вариантов возвращаемого значения, когда функция не нашла ничего, вычисление невозможно, произошла ошибка и т.п. Несмотря на то, что такого термина нет, данная идиома повсеместно используется всеми программистами на всех языках. И в этом коварство Javascript, потому что он предоставляет несколько вариантов для таких значений. Можно искренне порадоваться за вас, если вы всегда знаете, когда возвращать false, null, а когда undefined. Давайте теперь вместе порадуемся и за тех, кто не знает, но не унывает и пишет библиотеки, которыми нам с вами, возможно, придется пользоваться.

Очень многое зависит от того, что намудрил автор библиотеки. Библиотек много, качество разное, есть еще стандартные объекты, которые не всегда ведут себя логично. Какое отношение сторонние библиотеки имеют к самому Javascript? Никакого, если забыть, что до сих пор порой проще применять костыли вроде jQuery для того, чтобы ваш код работал независимо от браузера.

Типовая сторонняя функция, в зависимости от своей логики, может возвращать одно из следующих «пустых» значений:

  false      // старый-добрый булевский false
  undefined  // значение переменной, которая не была инициализирована
  null       // а это специальное значение для переменной инициализированной null
  NaN        // выдается при ошибках арифметических операций типа 1 / 0
  {}         // совсем не NULL, а пустой объект, но часто используется авторами как "пустое значение" по недосмотру
  []         // пустой массив, типичное дело для функций возвращающих список
  Object     // объект да и объект, оставьте его в покое

Выше было сказано, о проблемах библиотек, но далеко ходить не надо. Что из этого по вашему будет выдавать стандартный объект Date, если на вход ему передать неправильную дату? Ошибку? null? Нет — Object.

    var d = new Date("2012--12-01"); // объект будет создан без предупреждений

    d.toJSON();       // null
    d.getTime();      // NaN
    d.toString();     //'Invalid Date'
    d.toISOString()   // наконец-то бросит ошибку!

Поскольку выше было сказано, что на Javascript пишет много кто, то нередка ситуация, когда одну и ту же функцию С-шник напишет так:

  function isEven(arg) {
    if (arg % 2 == 0)
      return 1;
    else
      return 0;
  }

Любитель Perl выдаст

  function isEven(arg) {
    if (arg % 2 == 0)
      return 1;
    else
      return undefined;
  }

Программист на PHP может выдать и:

  function isEven(arg) {
    if (arg % 2 == 0)
      return 1;
    else
      return null;
  }

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

if (isEven(3) == false) {
    alert("ба, нечетное число!");
}

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

Но вернемся к обработке пустых значений. Допустим мы, отдавая дань моде, пишем некий сайт на nodejs. И решили мы сделать на нем регистрацию. Поскольку мы заботимся о безопасности, то хотим сделать длину пароля не менее 8 символов, и разрешим использовать в нем буквы в разном регистре и цифры. C Javascript это просто, ведь в язык встроены регулярные выражения:

  /^[a-zA-Z0-9]{8,}$/.test('passworD1'); // пароль подходит

Теперь давайте представим, что пароль приходит к нам откуда-то из запроса, или из какого-то input, если речь идет о клиент-сайде. И вот так получилось, что в результате опечатки или что-то напутав вы засунули в переменную с паролем что-то не то:

  var request = {
    "user"     : "alex",
    "password" : "sdjk23h78dg2"
  };

  // опа, опечатка
  if ( /^[a-zA-Z0-9]{8,}$/.test(request["pasword"]) ) {
    save_user_to_database(request);
  } 

И что характерно, пользователь сохранится. С пустым паролем, или только с солью, если таковая предусмотрена. Все это благодаря особенности Javascript:

  /^[a-z]{1,10}$/.test(null);      // true 
  /^[a-z]{1,10}$/.test(undefined); // true 
Строки и числа

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

Давайте посмотрим как происходит интерпретация таких чисел в строках в разных языках:

PHP 34 32
Python ошибка ошибка
Perl 34 32
Ruby ошибка ошибка
Javascript ‘331’ 32

Так что parseInt и parseFloat — ваши лучшие друзья. Хотя, лучше Number…

  // вы так, конечно, никогда не напишите, но зрелищно ведь, да?
  parseInt(null, 24) === 23 // true
Множественные аргументы

Забудьте свои перловые и питонячьи замашки с прохождением списка списка аргументов через pop, shift, или что вы там используете.

  function lastNegative() {
    var negative = null;

    while ( arguments.length ) {
      negative = arguments.pop(); // ошибка
      if (negative < 0)
        break;
    }

    return negative;
  } 

Несмотря на то, что локальная переменная arguments — массив неименованных аргументов функции, она не является полноценным массивом Javascript. И чтобы работать с ним привычным образом следует каким-то образом преобразовать ее в массив нормальный, класса Array. Самый частый сниппет используемый для этого имеет такой вид.

  var args = Array.prototype.slice.call(arguments);
Массивы

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

[]     == true // false - пустой массив, вроде бы интуитивно понятно
[1]    == true // true  - единица, все логично - и массив непустой, и единица приводится к true
[2]    == true // false - массив непустой, значит дело в единице, которая приводится к true... 
[true] == true // false - либо мы имеем хитрое правило приведения, либо true != true

// может все дело в магической единице ?
[1 , 1]  == true // false нет...

// а если вложенный массив?
[ [1], [1] ]    == true // false
[ [ [ [1] ] ] ] == true // true

// но !
new Array()    == true // false 
new Array(1)   == true // false 
new Array(1,2) == true // false 

Наверняка у профессионалов Javascript есть объяснение для всего этого, но проблема от этого не исчезнет. Утешает только то, что далеко не каждый гуру языка без гугла сможет объяснить почему:

  new Array([],null,undefined,null) == ",,,"; // true

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

Форматирование

Благодаря тому, что Javascript позволяет не ставить; в конце каждой строки, вы можете написать функцию, которая, в зависимости от форматирования, будет возвращать разные значения:

  function foo() {
    return "Феанор велел остановиться, раны его были смертельны и он знал, что час его близок";
  }
 
function bar() {
  return 
    "Феанор велел остановиться, раны его были смертельны и он знал, что час его близок";
}

foo(); // возвращает текст
bar(); // возвращает undefined 
Оператор foreach

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

for (index in ARRR) {
    var value = obj[index];
    console.log(value); 
}

А Кто-то любит использовать дедовский метод:

for (var index = 0; index < a.length; ++index) {     
    console.log(a[index]); 
}

Для тех кто хочет умилостивить колбэчное божество есть свой вариант:

a.forEach(function(entry) {
    console.log(entry);
});

У меня есть свой сниппет для такого цикла, а у вас? Не находите, что когда в языке есть целых 3 способа(не считая собственные foreach для каждой уважающей себя библиотеки), делать одну и ту же вещь базовую вещь, и каждому способу чего-то не хватает, то здесь что-то не так? Не находите? А это все потому, что вы пишите на Javascript. Но все же задумайтесь почему в CoffeeScript и Dart таки поддерживается итерация по значению списка:

# dart
for (var x in collection) {   
    print(x); 
}

# coffeescript
for x in collection
    print(x)

source

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: