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


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


«Сумма пустот»


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


[void 0, null, []].join("") == false // => true
[void 0, null, []].join("") === "" // => true

// Не работает при сложении со строкой.
void 0 + "" // => "undefined"
null + "" // => "null"
[] + "" // => ""

На практике такое поведение можно использовать для отсева действительно пустых данных


var isEmpty = (a, b, c) => {
    return ![a, b, c].join("");
}

var isEmpty = (...rest) => {
    return !rest.join("");
}

isEmpty(void 0, [], null) // => true
isEmpty(void 0, [], null, 0) // => false
isEmpty(void 0, [], null, {}) // => false. С пустым объектом такой трюк не проходит

// Или так, в случае если аргумент один
var isEmpty = (arg) => {
    return !([arg] + "");
}

isEmpty(null) // => true
isEmpty(void 0) // => true
isEmpty(0) // => false

«Странные числа»


Попытка определить типы для NaN и Infinity при помощи оператора typeof как результат вернет "number"


typeof NaN // => "number"
typeof Infinity// => "number"
!isNaN(Infinity) // => true

Юмор в том, что NaN — это сокращение от "Not-A-Number", а бесконечность (Infinity) сложно назвать числом.


Как вообще тогда определять числа? Проверить их конечность!


function isNumber(n) {
    return isFinite(n);
}

isNumber(parseFloat("mr. Number")) // => false
isNumber(0) // => true
isNumber("1.2") // => true
isNumber("abc") // => false
isNumber(1/0) // => false

«Для отстрела ноги возьмите объект»


Для javascript Object — одна из самых первых структур данных и в тот же момент, на мой взгляд, — король хитросплетений.


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


В противном случае, в итерацию могут попасть свойства из расширения прототипа.


Object.prototype.theThief = "Альберт Спика";
Object.prototype.herLover = "Майкл";

var obj = {
    theCook: "Ричард Борст",
    hisWife: "Джорджина"
};

for (var prop in obj) {
    obj[prop]; // Цикл обойдет: "Ричард Борст", "Джорджина", "Альберт Спика", "Майкл"

    if (!obj.hasOwnProperty(prop)) continue;

    obj[prop]; // Цикл обойдет: "Ричард Борст", "Джорджина"
}

Между тем, Object можно создать и без наследования прототипа.


// Несложная инструкция по прострелу ноги
var obj = Object.create(null);
obj.key_a = "value_a";
obj.hasOwnProperty("key_a") // => Выбросит ошибку.

"Эй, кэп, а зачем это нужно?"


В таком хэше отсутствуют наследуемые ключи — только собственные (гипотетическая экономия памяти). Так, проектируя API к библиотекам, где пользователю позволено передавать собственные коллекции данных, про это легко забыть — тем самым выстрелить себе в ногу.


И так как в таком случае вы не можете контролировать вводимые данные, необходим универсальный способ проверять собственные ключи в объекте.


Способ первый. Можно получить все ключи. Неоптимальный, если выполнять indexOf внутри цикла: лишний обход массива.


Object.keys(obj); // => ["key_a"]

Способ второй. Вызывать метод hasOwnProperty с измененным контекстом


Object.prototype.hasOwnProperty.call(obj, "key_a") // => true

Казалось бы, вот он идеальный способ. Но, Internet Explorer.


// Выполнять в IE

// Создать объект без прототипа
var obj = Object.create(null);
obj[0] = "a";
obj[1] = "b";
obj[2] = "c";

Object.prototype.hasOwnProperty.call(obj, 1); // => false
Object.prototype.hasOwnProperty.call(obj, "1"); // => false
Object.keys(obj); // => ["0", "1", "2"]

obj.a = 1;

Object.prototype.hasOwnProperty.call(obj, 1); // => true
Object.prototype.hasOwnProperty.call(obj, "1"); // => true

// Случай когда объект создается с прототипом от Object
obj = Object.create(Object.prototype);

obj["2"] = 2;
obj.hasOwnProperty("2"); // => false

obj.a = "a";
obj.hasOwnProperty("2"); // => true

delete obj.a;

obj.hasOwnProperty("2"); // => false

Вам не показалось, IE действительно отказывается проверять цифровые ключи в объектах созданный через Object.create(), до тех пор, пока в нем не появится хотя бы один строчный.


И этот факт портит весь праздник.


UPD:
Решение предложенное Дмитрием Коробкиным


UPD:
bingo347 справедливо заметил, что если не писать скрипты для "динозавров", то перебор собственных свойств целесообразней выполнять при помощи Object.keys(obj) и Object.getOwnPropertyNames(obj)


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


Object.keys([1, 2, 3]); // => ["0", "1", "2"]
Object.getOwnPropertyNames([1, 2, 3]); // => ["0", "1", "2", "length"]

«лже-undefined»


Часто разработчики проверяют переменные на undefined прямым сравнением


((arg) => {
    return arg === undefined; // => true
})();

Аналогично поступают и с присваиванием


(() => {
    return {
        "undefined": undefined
    }
})();

"Засада" кроется в том, что undefined можно переопределить


((arg) => {
    var undefined = "Happy debugging m[\D]+s!";
    return {
        "undefined": undefined,
        "arg": arg,
        "arg === undefined": arg === undefined, // => false
    };
})();

Эти знания лишают сна: получается, что можно сломать весь проект, просто переопределив undefined внутри замыкания.


Но есть пара надежных способов сравнить или назначить undefined — это использовать оператор void или объявить пустую переменную


((arg) => {
    var undefined = "Happy debugging!";
    return {
        "void 0": void 0,
        "arg": arg,
        "arg === void 0": arg === void 0 // => true
    };
})();

((arg) => {
    var undef, undefined = "Happy!";
    return {
        "undef": undef,
        "arg": arg,
        "arg === undef": arg === undef // => true
    };
})();

«Сравнение Шрёдингера»


Однажды коллеги поделились со мной интересной аномалией.


0 < null; // false
0 > null; // false
0 == null; // false
0 <= null; // true
0 >= null // true

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


В то время как равенство чисел с null всегда возвращает false.


Если принять во внимание, что null после приведения в число становится +0, внутри компилятора сравнение приблизительно выглядит так:


0 < 0; // false
0 > 0; // false
0 == null; // false
0 <= 0; // true
0 >= 0 // true

Сравнение чисел с Boolean


-1 == false; // => false
-1 == true; // => false

В javascript при сравнении Number с Boolean, последний приводится к числу, после производится сравнение Number == Number.


И, так как, false приводится к +0, а true приводится к +1, внутри компилятора сравнение обретает вид:


-1 == 0 // => false
-1 == 1 // => false

Однако.


if (-1) "true"; // => "true"
if (0) "false"; // => undefined
if (1) "true"; // => "true"

if (NaN) "false"; // => undefined
if (Infinity) "true" // => "true"

Потому что 0 и NaN всегда приводятся к false, все остальное true.


Проверка на массив


В JS Array наследуются от Object и, по сути, являются объектами с числовыми ключами


typeof {a: 1}; // => "object"
typeof [1, 2, 3]; // => "object"
Array.isArray([1, 2, 3]); // => true

Штука в том, что Array.isArray() работает только начиная с IE9+


Но есть и другой способ


Object.prototype.toString.call([1, 2, 3]); // => "[object Array]"

// Соответственно
function isArray(arr) {
    return Object.prototype.toString.call(arr) == "[object Array]";
}

isArray([1, 2, 3]) // => true

Вообще используя Object.prototype.toString.call(something) можно получить много других типов.


UPD:
boldyrev_gene задал, на мой взгляд, хороший вопрос: почему не использовать instanceof?


Экземпляры массива созданные внутри фреймов и других окон будут иметь разные экземпляры конструкторов.


var 
    iframe = document.querySelector("iframe"),
    IframeArray = iframe.contentWindow.Array;

new IframeArray() instanceof Array; // => false
Array.isArray(new IframeArray()); // => true
Object.prototype.toString.call(new IframeArray()); // => "[object Array]"

arguments — не массив


Настолько часто забываю об этом, что решил даже выписать.


(function fn() {
    return [
        typeof arguments, // => "object"
        Array.isArray(arguments), // => false
        Object.prototype.toString.call(arguments) // => "[object Arguments]";
    ];
})(1, 2, 3);

А так как arguments — не массив, то в нем недоступны привычные методы .push(), .concat() и др. И в случае если нам необходимо работать с arguments как с коллекцией, существует решение:


(function fn() {
    arguments = Array.prototype.slice.call(arguments, 0); // Превращение в массив
    return [
        typeof arguments, // => "object"
        Array.isArray(arguments), // => true
        Object.prototype.toString.call(arguments) // => "[object Array]";
    ];
})(1, 2, 3);

а вот ...rest — массив


(function fn(...rest) {
    return Array.isArray(rest) // => true. Oh, wait...
})(1, 2, 3);

Поймать global. Или определяем среду выполнения скрипта


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


И так как в JS не предусмотрен механизм определения среды выполнения на уровне стандартной библиотеки, можно сделать финт используя особенность поведения указателя внутри анонимных функций в нестрогом режиме.


В анонимных функциях указатель this ссылается на глобальный объект.


function getEnv() {
    return (function() {
        var type = Object.prototype.toString.call(this);

        if (type == "[object Window]")
            return "browser";

        if (type == "[object global]")
            return "nodejs";
    })();
};

Однако в строгом режиме this является undefined, что ломает способ. Этот способ актуален в случае если global или window объявлен вручную и глобально — защита от "хитрых" библиотек.




Спасибо за внимание! Надеюсь, кому-нибудь эти заметки пригодятся и послужат пользой.

Поделиться с друзьями
-->

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


  1. xuexi
    20.10.2016 16:56
    -3

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


  1. Aingis
    20.10.2016 17:46
    +2

    Приходиться делать «костыль» вроде такого
    if (Object.prototype.isPrototypeOf(obj)) {
        return obj.hasOwnProperty(prop);
    }
    return prop in obj;
    А чем вас изначально не устроил оператор in? Он же идеален для таких проверок наличия свойств в объектах без прототипов. hasOwnProperty по сути нужен только для цикла for..in (и то не всегда).


    1. Ohar
      20.10.2016 18:09

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


      1. torbasow
        21.10.2016 11:57
        +1

        А! Дайте ссылку на то, что arguments не рекомендуется использовать! Помню, что что-то такое видел, но обыскался.



    1. EugeneGantz
      20.10.2016 19:17
      +1

      Наследование может быть, но не от Object.


      var fake = Object.create(null);
      
      fake.key_a = "value_a";
      
      fake.hasOwnProperty = function() {
          return true;
      }
      
      var obj = Object.create(fake);
      
      Object.prototype.isPrototypeOf(obj); // => false
      obj.hasOwnProperty("key_a"); // => true
      Object.prototype.hasOwnProperty.call(obj, "key_a"); // => false
      "key_a" in obj; // => true
      Object.keys(obj); // => []

      Поправьте, если ошибся.


      1. Aingis
        21.10.2016 12:52
        -1

        Я вас спросил, почему сразу не использовать for..in для объектов, созданных через Object.create(null). Зачем вы приплели здесь прототипы, когда вопрос был изначально про беспрототипные объекты?

        Если объект создан с использованием прототипа, то это инстанс, который через for..in никто не обрабатывает. А если внезапно захочется, то просто сделают методы прототипа неперечисляемыми. И кроме методов в прототипы никто, как правило, ничего не пишут — это правила хорошего тона.

        for..in используют для пустых объектов, обычно простой литерал {}, но если есть риск, что прототип будет засран (говнорасширение в браузере или всякая малварь, но всё равно риск мал), то есть hasOwnProperty и Object.create(null). Вы с этого начали. Почему через минуту вы об этом забываете? В контролируемом окружении, как-то Node.js, можно вообще никаких проверок не делать, всё будет в порядке и даже работать в два раза быстрее.

        Ваши жалобы в стиле «если писать говнокод, то выходит говно» непонятны. Не пишите говнокод. Судя по комментариям, вы знаете все язык, но почему-то до вас никак не доходит как надо на нём писать. Требование понимать, что ты пишешь и что хочешь получить, никто не отменял. В?нём и заключается суть программирования.


        1. EugeneGantz
          22.10.2016 16:17

          Пример в статье был приведен для случая с IE.
          С другой стороны, в него закралась ошибка: пример справедлив для всех случаев, когда объект создается через Object.create().
          Исправил.


          Спасибо за развернутые замечания.


  1. dom1n1k
    20.10.2016 17:51
    +8

    С проблемой переназначения undefined есть хорошее решение — ничего не делать.
    «А вот можно еще спрыгнуть с моста и тогда...» — «А не надо прыгать с моста» :)


    1. a-motion
      21.10.2016 08:32

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

      Так вот совет «так не делать» имеет очень ограниченное применение из-за особенной способности человеческого мозга ошибаться. Именно поэтому в тренде строгая типизация (а ведь можно было бы просто не передавать неправильные типы, правда?), чистые функции (а ведь можно было бы просто не гадить в универсум и не менять свойства объекта глубоко изнутри функции вывода лога) и так далее.

      Я легко могу себе представить джуниора, который по запарке объявит локальную переменную `undefined`. Да, не пройдет CR. Да, нынче надо `var`, `let`, или что там впереди написать. Но это не убирает грабли, а маскирует их.


  1. faiwer
    20.10.2016 17:56
    +2

    В очередной раз наткнулся на void 0. И снова стал терзать меня старый вопрос ? а для чего этот самый void вообще задумывался. Ну всяко же не для получения настоящего undefined-а. Лучшее что я пока накопал, это использование в рамках какого-нибудь однострочника, который что-нибудь делает, но так, чтобы результат однострочника был undefined. Т.е. грубо говоря a => void (some = a) вместо a => { some = a; }. Какой-то уж совсем бредовый use case. Не находите? Не могли же его и в самом деле придумать ради href="javascript:void(anything)"?


    1. vintage
      20.10.2016 21:09

      Скорее для навешивания обработчиков через аттибуты.


  1. PerlPower
    20.10.2016 17:57

    А разгадка одна.


    1. vitvad
      20.10.2016 18:41
      +11

      Есть два типа языков программирования: те, которые люди постоянно ругают, и те, которыми никто не пользуется.


      1. PerlPower
        21.10.2016 00:17
        -5

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


    1. Louter
      21.10.2016 16:28

      «Похволяет писать плохо» != «плохой язык»
      Строгие сравнения и явные приведения избавляют от множества багов связанных с вышеописанным)


  1. bingo347
    20.10.2016 18:16
    +1

    В то время как обычное равенство при наличии null в сравнении всегда возвращает false.
    Вы случайно не спутали с NaN?
    null == null // true
    null === null // true
    NaN == NaN //false
    


    Насчет for-in цикла, использовать его, вообще плохая практика, кроме описанных Вами проблем, он еще и отключает оптимизатор во всех актуальных сегодня компиляторах (v8, SpiderMonkey, Chakra)
    Вместо него лучше использовать
    //es2015 simple
    for(let key of Object.getOwnPropertyNames(object)) {}
    
    //es5
    for(var keys = Object.getOwnPropertyNames(object), i = 0, key = keys[0]; i < keys.length; i++, key = keys[i]) {}
    
    //es2015 object iterator
    function* objectIterator(object) {
      for(let key of Object.getOwnPropertyNames(object)) {
        yield [key, object[key]];
      }
    }
    for(let [key, value] of objectIterator(object)) {}
    


    Любые манипуляции с arguments кроме чтения по индексам выключает оптимизацию в v8 и SpiderMonkey (про Chakra точно сказать не могу)
    То же истинно для переменных-аргументов функции
    То же истинно для передачи arguments во внешние функции или сохранения его в другую переменную, но есть исключение — 2 аргумент нативного Function.prototype.apply
    То есть так — func.apply(this, arguments) — нормально

    Хак для получения global объекта в strict режиме:
    function getGlobal() {
      return (new Function('return this;'))();
    }
    


    Ну и напоследок, нестрогие сравнения — дурной тон, мало того что работают в разы медленнее, так еще подвержены ошибкам


    1. yulllll
      20.10.2016 19:02

      «нестрогие сравнения — дурной тон, мало того что работают в разы медленнее»
      подскажите, где найти подробности об этом?


      1. Keyten
        20.10.2016 19:39
        +1

        Про конкретное время работы не скажу, но строгие сравнения не приводят типы, поэтому они быстрее.


    1. Aingis
      20.10.2016 19:15

      Что-то вы странное пишете.

      Про null, очевидно, имелось в виду сравнение с нулём (и числами). Он, как известно, нестрого равен только null и undefined. И это здорово. Можно использовать для проверки аргументов и возможности обращения к свойствам, что особенно полезно в случаях, когда 0 или "" — вполне легальные значения.

      Насчет for-in цикла, использовать его, вообще плохая практика, кроме описанных Вами проблем, он еще и отключает оптимизатор во всех актуальных сегодня компиляторах (v8, SpiderMonkey, Chakra)
      Сколько не узнавал про работу компиляторов, такое вижу впервые. Особенно удивляет утверждение, что работающий через итератор for..of вдруг оказался быстрее. Откуда такая информация? Особенно пикантно смотрится комментарий «es2015 simple» с for..of из es2016.

      Если уж говорить про ES2016, то логичнее упомянуть Map и Set, которые оптимизированы для хранения.

      Ваш хак для получения global объекта в strict-режиме обломается на политиках CSP, запрещающих eval.
      Ну и напоследок, нестрогие сравнения — дурной тон, мало того что работают в разы медленнее, так еще подвержены ошибкам.
      Ни разу не видел ошибок из-за нестрого сравнения. Зато несколько раз встречал ошибки из-за обязательного насаждения строгого. Например, когда сравнивается 2 и '2'. Сколько не спрашивал, никто не смог привести примеров, когда нестрогое сравнение приводило к ошибкам. Может вы сможете? (Только реальный пример, а не говнокод.)

      Правила сравнения примитивов просты и легко запоминаются, тем более что с булевыми значениями сравнивать не надо почти никогда. Объекты с примитивами сравнивать не нужно вовсе, если у вас не говнокод, а при сравнении объектов (т.е. не примитивов) между собой нестрогое сравнение ничем не отличается от строго. Особенно глупо выглядит строгое сравнение с кодом, выдающим заведомо известный тип, вроде typeof или [].length. Это называется «мне лень изучить основы языка, на котором я пишу».

      Откуда информация про «в разы медленнее»? При одинаковых типах, всё алгоритм сравнения идентичен, а при разных типах вы наверняка не учли предварительные преобразования вроде Number('2'), которые всё равно понадобятся для сравнения, вас же интересует сравнение по сути, а не узнавание того факта, что типы разные.

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


      1. faiwer
        20.10.2016 21:47

        Ни разу не видел ошибок из-за нестрого сравнения

        У меня строго наоборот. Пример из жизни. Код, в котором изначально забили на строгое сравнение привёл к тому, что расплодились местами String-ые id-ки, а местами Number-ие. Всё это работало на mock-сервере. Стоило подключить backend со Scala-й и Cache, как мне пришлось выискивать и исправлять множество багов, ибо на сервере в лучшем случае всё дохло от несоответствия типов модели. А это ещё попробуй всё отследи, если кодовая база уже достаточно большая.


        А ещё тот же lodash сверяет всё строго, и можно часами отлавливать какой-нибудь идиотский баг где _.filter(collection, { field: value }) не будет работать именно из-за этого. Оно ведь не упадёт. Оно просто ничего не найдёт. Полагаю библиотечные indexOf и прочие методы тоже дадут прикурить.


        Суть — это всё ведёт к беспорядку. И на большой кодовой базе может стоить много денег. Стараюсь всегда когда это возможно использовать строгое равенство.


        1. Aingis
          20.10.2016 23:17
          -2

          Так это ошибки из-за говнокода, даже и не в JS. Если писать говнокод, то проблемы всегда будут, но это не проблема языка, говнокод можно написать на чём угодно.


          1. faiwer
            21.10.2016 06:51

            Так вот оно в чём дело. Что же вы раньше то не сказали? Код который вы пишете не пахнет. Поэтому и проблемы нестрогих неравенств нет. Как и любых других проблем нет. Это у нас просто руки кривые. Теперь понятно. Ну извиняйте нас болезных, грешны мы.


            1. Aingis
              21.10.2016 11:37

              Рассказываю. Будь у вас строгое равенство, ваш код из

              if (sample.id == id) ...
              

              превратился бы в случайно раскиданные
              if (Number(sample.id) === id) ...
              if (String(sample.id) === id) ...
              

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

              А если смотреть причину, то она в том, что бардак в разработке: не закреплены стандарты API и форматы данных. Говнокод — уже даже следствие этого хаоса.

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


              1. faiwer
                21.10.2016 11:43

                На ваши данные это не повлияло бы никак

                Глупости то какие. if('2' !== 2); и if('2' != 2) это разные вещи. В случае нестрогих неравенств ваш код будет жить в тех ситуациях, когда нормальный код поломается. Это как в c++ можно выйти за пределы массива указав избыточный индекс элемента, исправить там что-нибудь, а потом ловить баги в совершенно неожиданных местах (потому что чёрт знает что вы там изменили в этой куче, то).


                По поводу "хуже читаться" пассажа я не понял. Что за нелепая вкусовщина.


                1. Aingis
                  21.10.2016 13:54

                  В случае нестрогих неравенств ваш код будет жить в тех ситуациях, когда нормальный код поломается.
                  Правильно, но ведь вам надо сравнивать значения переменных, а не типов. Если конечно у вас не такой «прекрасный» API, что 2 и '2' совсем разные сущности. Это приводит к тому, что я написал, везде по коду будут раскиданы:
                  if (Number(sample.id) === id) ...
                  if (String(sample.id) === id) ...
                  
                  Что вы тут комментировали, и какой дополнительный смысл вкладывали, я не понял.

                  Ну, а на вопрос что легче прочитать:
                  if (Number(sample.id) === id) ...
                  
                  или
                  if (sample.id == id) ...
                  
                  я даже не знаю что ещё написать. Это очевидно.


                  1. faiwer
                    21.10.2016 14:11

                    что 2 и '2' совсем разные сущности

                    А должно быть иначе?


                    Это приводит к тому … везде по коду будут раскиданы … if (Number(sample.id) === id)

                    Пардон, не углядел Number(). Ок. Ну если вы целенаправлено будете убегать от проблемы такими вот хаками, то я не удивлюсь, если у вас всё развалится.


                    А теперь немного магии. Вы изначально пишете: if(a === b) и все счастливы. А если возникает необходимость приводить типы к друг другу, то вытаскиваете себя из этого болота за волосы, и переходите, наконец, на content-type: json.


                    1. a-motion
                      21.10.2016 15:41

                      > > что 2 и '2' совсем разные сущности

                      > А должно быть иначе?

                      Ну вот, сошлись, коса и камень. Нет никакого «должно быть». Если это API для ваших дорогих клиентов — то да, должно быть иначе. Если это внутренний микросервис, который нагорячую фигачит в базу — «иначе» нельзя ни в коем случае. Если это какой-то ваш внутренний кухонный комбайн, то должно быть так, как удобнее в 80% вариантов использования.

                      Я, например, когда имплементирую внутреннее API, всегда принимаю и 2 и '2' (а часто и «два» и `Два.new` и `id_of_2`), проверяю, что это такое к нам пожаловало и реагирую соответственно. Так что надо сначала обговорить предметную область обсуждения, а потом уже решать, можно ли считать 2 и '2' — одним и тем же. Подсказка: при обработке консольного ввода хоть у вас там наистрожайшая типизация, придется считать их одним и тем же.


                      1. faiwer
                        21.10.2016 16:34
                        +2

                        Даже если я по какой-то неведомой причине буду писать js-backend сервис с API для дорогих клиентов, которые не могут совладать с типами в своих "json-запросах", поддержку их кривых запросов я буду делать на уровне адаптера. А вовсе не на уровне строгих-нестрогих неравенств в коде.


                        Для того, чтобы использовать == вместо === необходима веская причина. Второе ведёт к прилежному коду, ранним ошибкам (вместо поздних трудно вылавливаемых). Первое иногда может привести к доп. удобствам. Но только лишь иногда. Скажем if(!some) куда удобнее чем if(some === undefined || some === null || some === ''). Такие вещи действительно читаемее. Но и тут не стоит забывать про 0 :)


                        1. a-motion
                          21.10.2016 19:29

                          Не-не-не, вы меня неверно поняли. Я согласен с вами целиком и полностью, я лишь отреагировал на фразу [вне контекста, что зря] «А должно быть иначе?».

                          Иногда — должно, вот все, что я хотел сказать. Так-то я сам всегда использую `===` и даже вот этот ужас внутри `if` (обернутый в `check(UNDEFINED, NULL)`).


                          1. faiwer
                            21.10.2016 19:30

                            Иногда — должно

                            С этим спорить не буду. Ситуации бывают разные.


      1. faiwer
        20.10.2016 21:52
        +2

        мне лень изучить основы языка, на котором я пишу

        Надо же. А я всегда думал, что это называется: я не хочу наступать на одни и те же грабли n-жды. Я даже в тернарном операторе избегаю конструкций больше одного операнда, не обернув их скобками (для наглядности и очевидности). В моём понимании хороший код должен быть очевидным и без заучивания таблицынеправильныхглаголовтаблицы приведения типов.


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


      1. Louter
        21.10.2016 16:43
        +1

        Про передачу arguments в другие функции
        https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments

        For… in не просто работает с объектом, как с хэш-таблицей, пробегая ещё по родителям ища перечисляемые типы. getOwnPropertyNamies же ищет перечисляемые только для объекта, которые могут вполне храниться в отдельном векторе, что удобнее.

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


    1. mayorovp
      20.10.2016 19:38
      -4

      Любые манипуляции с arguments кроме чтения по индексам выключает оптимизацию в v8 и SpiderMonkey (про Chakra точно сказать не могу)

      … но только в нестрогом режиме. В строгом режиме arguments — это обычный массив, только с другим прототипом.


      1. vintage
        21.10.2016 16:04

        Это в каком браузере он — обычный массив?


    1. EugeneGantz
      20.10.2016 23:47

      В то время как обычное равенство при наличии null в сравнении всегда возвращает false.

      Согласен, это был странный пассаж, ведь речь шла о числах. Спасибо, исправил.


      Относительно проверки собственный свойств.
      По идее, метод getOwnPropertyNames должен быть действительно быстрее: он не обходит свойства прототипов.
      К тому же, метод доступен IE9+


      Но если продолжать тему "пальбы по ногами", следует упомянуть, что свойства объектов могут обладать разными параметрами дескрипторов.
      Так, например, некоторые свойства могут быть неитерируемыми enumerable = false.


      var obj = Object.create(
          {}, 
          {
              "enum-0": { 
                  value: "1",
                  enumerable: true
              },
              "not-enum-1": { value: "0" },
              "not-enum-2": {
                  value: "1",
                  enumerable: false
              }
          }
      );
      
      Object.keys(obj); // => ["enum-0"]
      Object.getOwnPropertyNames(obj); // => ["enum-0", "enum-1", "enum-2"]
      Object.getOwnPropertyNames([1, 2, 3]); // => ["0", "1", "2", "length"]

      Как можно заметить, метод Object.getOwnPropertyNames() выводит все ключи без разбора. Вероятно, целесообразней использовать Object.keys(), но это зависит от требований к программе.


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


      Относительно строгости сравнения, не готов сейчас ступать на "тонкий лед": на вскидку, мне тяжело сейчас вспомнить случай, когда последний раз нестрогое сравнение ломало программу.
      Но случай когда вовремя неприведенные к нужному типу данные — вчера.


      // образный пример
      var uid = this.getAttribute("data-uid");
      stash[uid + 1] = true; // вместо 1 я получил "01"
      // можно легко забыть сделать stash[+uid + 1]

      Спасибо за развернутый комментарий.


    1. dom1n1k
      21.10.2016 15:37

      Кстати

      NaN == NaN // false
      вполне логично, если подумать. И я уверен, что это не баг дизайна языка, а фича.


      1. vintage
        21.10.2016 16:07

        Это стандарт IEEEчтототам. Хотя, я логики в этом не вижу.


        1. dom1n1k
          21.10.2016 16:48
          +1

          А логика очень простая.
          Очевидно же, что на практике никто никогда не будет сравнивать два NaN напрямую, это не имеет смысла.
          В реальности будут сравниваться некие переменные, которые должны быть числовыми, но периодически там по каким-то причинам могут проскакивать невалидные данные (которые и превращаются в коде в NaN). И при равенстве этих чисел должно что-то происходить.
          Но если невалидные данные пришли сразу в 2 переменные — то это две ошибки. Грубо говоря, это два «каких-то хз каких значения». И то что они оба «хз» не делает их равными по смыслу.
          И если бы два NaN были бы равны, это дало бы нам ложно-положительное срабатывание условия.


          1. vintage
            21.10.2016 23:21

            Совершенно не вижу логики почему тут всегда false.


            Number( parameter ) === NaN


            1. dom1n1k
              22.10.2016 00:00

              По вышеописанной. Замена == на === ситуацию не меняет. Потому и пришлось придумывать isNaN() — чтобы оградить от соблазна сравнивать два NaN-значения. Каждое их них нужно проверить отдельно. Повторюсь «хз что 1» не равно «хз что 2» и это вполне логично.


              1. vintage
                22.10.2016 00:38

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


                1. dom1n1k
                  22.10.2016 01:07

                  Не, тут разница в том, что 0 — это конкретная сущность. Она имеет понятное значение и прикладной смысл.
                  А NaN — это языковая условность для обозначения «нечта непонятного».


                  1. vintage
                    22.10.2016 01:47
                    -1

                    Ноль (нуль, от лат. nullus — никакой)

                    null, 0, undefined, NaN — всё это языковые условности, имеющие чёткие, определённые стандартом, значения и прикладной смысл.


            1. ivan386
              22.10.2016 03:02

              NaN (англ. Not-a-Number) — одно из особых состояний числа с плавающей запятой. Используется во многих математических библиотеках и математических сопроцессорах. Данное состояние может возникнуть в различных случаях, например, когда предыдущая математическая операция завершилась с неопределённым результатом, или если в ячейку памяти попало не удовлетворяющее условиям число.

              Свойства

              • NaN не равен ни одному другому значению (даже самому себе); соответственно, самый простой метод проверки результата на NaN — это сравнение полученной величины с самой собой.


              1. vintage
                22.10.2016 10:30
                -2

                И что вы хотели этим сказать?


            1. Keyten
              24.10.2016 20:11

              Потому что, если NaN будет нормально сравниваться, вы получите, например:

              Number(5 * 'foo') === Number(10 * 'bar');
              

              Что, очевидно, совсем не так.


              1. vintage
                24.10.2016 20:55
                -3

                А если 0 будет нормально сравниваться, вы получите, например:


                5 * 0 === 10 * 0

                Что, очевидно, совсем не так.


                1. Keyten
                  25.10.2016 11:32
                  +1

                  Да нет, с 0 как раз очевидно, что так.


                  1. vintage
                    25.10.2016 15:36
                    -3

                    Почему же с умножением строки на число результат для вас не очевиден?


                    1. a-motion
                      26.10.2016 07:25
                      +1

                      Потому что над полем вещественных чисел определена операция умножения, это настолько очевидно, что даже странно уточнять.


                      1. vintage
                        26.10.2016 07:53
                        -1

                        А над полем строк операция умножения не определена.


                        1. a-motion
                          26.10.2016 09:16
                          +1

                          Даже если отвлечься от того, что понятие «поле строк» существует только в вашей голове (и, вероятно, в книжках Бредбери), неопределенная операция умножения в точности означает, что результат не определен. Даже не знаю, как еще прозрачнее это сформулировать. Неопределен — значит ничему не равен. В том числе и самому себе. С делением на ноль та же фигня.

                          В некоторых языках, наоборот, операция умножения строки на число определена. Вот в руби, например:

                          'foo' * 5
                          #? "foofoofoofoofoo"
                          

                          Вот тут сравнивайте в свое удовольствие.

                          Впрочем, js и с делением на ноль выступает дегенеративненько:
                          5.0 / 0 === 10.0 / 0
                          true
                          


                          1. ivan386
                            26.10.2016 17:22

                            Потому что тут бесконечности а не NaN.

                            1/0 === -1/0  //false
                            


                            1. a-motion
                              26.10.2016 17:45
                              -1

                              А я и не говорил, что тут NaN. Я говорил, что JS ведет себя дегенеративно.

                              И да, я прекрасно понимаю, почему. Что не делает такое решение сколько-нибудь адекватным. Очевидно, что и для вещественных, и для целых (а других тут и нет) всегда выполняется

                              ? ? ?

                              Хороший смайл получился, кстати.


                              1. ivan386
                                26.10.2016 19:02
                                +2

                                Бесконечность

                                В программировании

                                Стандартная арифметика с плавающей запятой (IEEE 754-2008) содержит особые значения для +? и ??: порядок состоит из одних единиц (11…11), мантисса из одних нулей (00…00). Положительная бесконечность больше любого конечного числа, отрицательная — меньше любого. Операции с бесконечностью определяются особо: (+?) + x = +?, +? + (+?) = +?, +? ? ? = NaN, log (+?) = +?, sin (+?) = NaN, и т. д.


                                +? = +?
                                -? = -?
                                +? ? -?


                                1. a-motion
                                  26.10.2016 19:24
                                  -1

                                  Вы в следующий раз не на Вику, а сразу на пикабу ссылку давайте.

                                  Я же сказал: я прекрасно понимаю, почему так. Потому что разработчики некоторых языков не очень образованы и/или умны. Стандартная арифметика с плавающей запятой и мантиссой умерла в Фортране, лет 30 тому назад. Если бы все пользовались теми представлениями, которые удобны машине, у нас бы такие фокусы с флоатами до сих пор были, что ах, и знаки одного популярного в узких кругах числа мы бы до сих пор считали на бумажке.

                                  Но нет, люди с тех пор научились BigDecimal, Rational, и так далее. Я бы посмотрел на ваше лицо, если бы ваш банк считал все флоатами и вот такими бесконечностями, которые даже не консистентны.

                                  Есть масса нормальных языков, пригодных для вычислений. И есть вот это недоразумение для «кнопки подвигать в браузере». И не нужно сюда, пожалуйста, притягивать за уши безумные реплики из середины прошлого века про IEEE 754-2008, ладно?


                                  1. ivan386
                                    26.10.2016 20:34

                                    Т.е. вы хотите сказать что какой-то другой язык программирования может решить задачу 1/0 не используя бесконечности? Да хрен с ним языком. Эта задача вообще решается без бесконечностей?


                                    1. vintage
                                      26.10.2016 20:50
                                      -1

                                      Она и с бесконечностями не решается. То, что написано в IEEE — полнейшая глупость с точки зрения математики.


                                    1. a-motion
                                      26.10.2016 20:50
                                      +1

                                      Нет, такого я сказать не хотел, хотя в мире навалом полных непротиворечивых алгебр, в которых бесконечность — совершенно равноправный член социума. Также легко определить полную непротиворечивую алгебру, в которой 1/0 будет чем угодно. Но это все тут вообще ни при чем.

                                      Я же хотел сказать, что ? = ? автоматически делает аксиоматику поля рациональных чисел противоречивой. Что, как бы, не совсем комильфо. Потому что вместе с ? + x = ? оно жить не может. Поэтому правильным является подход в котором 1 / 0 ? 1 / 0 ? 2 / 0 ? ?. Только тогда _пользователь_ этого кода сможет не писать разлапистых ифов на каждую бесконечность.


  1. Rajken
    20.10.2016 18:44
    +1

    javascript — это диагноз. Зачем сравнение с void 0, typeof(x) == «undefined» отменили?


    1. Louter
      21.10.2016 16:47

      короче запись =) А вообще в CS можно писать эти самые undefined он сам в void 0 трансформирует)


      1. Aingis
        21.10.2016 17:13

        someVar != null
        ещё короче, но адепты строго сравнения начинают корчиться и изрыгать проклятия.


  1. ua9msn
    20.10.2016 18:50
    -1

    var obj = Object.create(null);
    obj.key_a = «value_a»;
    obj.hasOwnProperty(«key_a») // => Выбросит ошибку.

    Ошибка тут в другом месте. Вы наследуете от null, а hasOwnProperty находится в прототипе Object.


    1. DenimTornado
      20.10.2016 21:47
      -1

      Ну если уж на то пошло, null — тоже объект)


  1. abyrkov
    20.10.2016 19:20
    +2

    Ну вот, опять скука.
    Правила приведения?
    На самом деле они абсолютно верны — информация не теряется. Т.е.

    Number(Boolean(34)) == 34 // => false
    Boolean(Number(true)) == true // => true
    

    Другое из раздела — аналогично.
    Возьмем факт, что arguments — не массив. И? String тоже не массив, и NodeList тем паче. И никого не смущает, потому, что они лишь преопределяют скобки.

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


  1. boldyrev_gene
    20.10.2016 19:20
    +1

    Штука в том, что Array.isArray() работает только начиная с IE9+

    Простите, а код
    [1, 2, 3] instanceof Array
    

    не знаете в каких браузерах может не работать?


    1. EugeneGantz
      20.10.2016 19:23
      +5

      new iframe.contentWindow.Array() instanceof Array // => false
      Array.isArray(new iframe.contentWindow.Array()) // => true
      Object.prototype.toString.call(new iframe.contentWindow.Array()) // => "[object Array]"

      Экземпляры массива созданные внутри фреймов и других окон будут иметь разные экземпляры конструкторов


      Кстати говоря, MDN предлагает в качестве полифила toString решение


      1. boldyrev_gene
        20.10.2016 19:29

        Спасибо за информацию. Хотя конечно никогда не приходилось работать с фреймами.


      1. bingo347
        20.10.2016 20:31

        + к Вашему комменту добавлю, что в node.js разные контексты VM приведут к такому же результату, а так же это истинно для любых объектов, а не только массивов
        Все что делает оператор instanceof — проверяет, что в цепочке прототипов объекта слева от него присутствует ссылка на прототип конструктора справа


      1. gearbox
        20.10.2016 20:49
        +1

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


  1. mayorovp
    20.10.2016 19:34

    (комментарий был удален)


  1. VolCh
    20.10.2016 22:05
    +1

    NaN и Ifninity — это не типы, а специальные значения типа Number. И это не только в ДжС, а во всех типа х всех языков, полностью поддерживающих стандарт представления чисел с плавающей запятой IEEE… не помню.


  1. jbubsk
    21.10.2016 00:03

    Похоже на осеннее обострение аллергии на javascript. Ну, ничего, к зиме пройдёт.


  1. vmb
    21.10.2016 00:11

    По поводу последнего, контекст можно ещё и так проверять, кажется:


    if (typeof window !== 'undefined') {
      // browser
    } else {
      // Node.js
    }


  1. Large
    21.10.2016 00:40
    +3

    Советы из анабиоза. Это всегда было плохим стилем, а теперь так вообще не актуально. Спецификация ES5 запрещает переопределять undefined. Для колекций лучше использовать Map или Set которые есть во всех актуальных версиях браузеров (и без проблем полифилятся массивами), а не насиловать браузеры вызывая деоптимизацию. Магии нет, есть спецификация и преобразование типов достаточно с ними разобраться и больше не смеяться с видео javascript WTF. Например массив с join не конвертирует пустые значения в пустую строку, а тупо их пропускает. Уже IE10 устаревший браузер, прекращайте поддерживать IE8 и всю эту китайщину. Если прямо нужно — лучше использовать shim.


  1. Steinmar
    21.10.2016 18:39
    +1

    this это же ссылка, в js нет указателей, не?


  1. ymatuhin
    25.10.2016 22:33

    Еще одна статья в духе JavaScript — говно.
    Лучше бы написали как на этом «говне» писать качественный код.