Pull to refresh

Comments 79

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

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

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

Наследование может быть, но не от 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); // => []

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

UFO just landed and posted this here

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


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

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

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

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

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

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

UFO just landed and posted this here
Есть два типа языков программирования: те, которые люди постоянно ругают, и те, которыми никто не пользуется.
UFO just landed and posted this here
«Похволяет писать плохо» != «плохой язык»
Строгие сравнения и явные приведения избавляют от множества багов связанных с вышеописанным)
В то время как обычное равенство при наличии 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;'))();
}


Ну и напоследок, нестрогие сравнения — дурной тон, мало того что работают в разы медленнее, так еще подвержены ошибкам
«нестрогие сравнения — дурной тон, мало того что работают в разы медленнее»
подскажите, где найти подробности об этом?
Про конкретное время работы не скажу, но строгие сравнения не приводят типы, поэтому они быстрее.
UFO just landed and posted this here
Ни разу не видел ошибок из-за нестрого сравнения

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


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


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

UFO just landed and posted this here

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

UFO just landed and posted this here
На ваши данные это не повлияло бы никак

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


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

UFO just landed and posted this here
что 2 и '2' совсем разные сущности

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


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

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


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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

В то время как обычное равенство при наличии 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]

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

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

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

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

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


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

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

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

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

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

Свойства

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

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

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

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

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

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


5 * 0 === 10 * 0

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

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

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

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

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

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

В некоторых языках, наоборот, операция умножения строки на число определена. Вот в руби, например:
'foo' * 5
#⇒ "foofoofoofoofoo"

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

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

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

∞ ≠ ∞

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

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

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


+∞ = +∞
-∞ = -∞
+∞ ≠ -∞

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

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

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

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

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

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

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

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

javascript — это диагноз. Зачем сравнение с void 0, typeof(x) == «undefined» отменили?
короче запись =) А вообще в CS можно писать эти самые undefined он сам в void 0 трансформирует)
UFO just landed and posted this here
var obj = Object.create(null);
obj.key_a = «value_a»;
obj.hasOwnProperty(«key_a») // => Выбросит ошибку.

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

UFO just landed and posted this here
Ну вот, опять скука.
Правила приведения?
На самом деле они абсолютно верны — информация не теряется. Т.е.
Number(Boolean(34)) == 34 // => false
Boolean(Number(true)) == true // => true

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

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

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

не знаете в каких браузерах может не работать?
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 решение

Спасибо за информацию. Хотя конечно никогда не приходилось работать с фреймами.
+ к Вашему комменту добавлю, что в node.js разные контексты VM приведут к такому же результату, а так же это истинно для любых объектов, а не только массивов
Все что делает оператор instanceof — проверяет, что в цепочке прототипов объекта слева от него присутствует ссылка на прототип конструктора справа
Можно добавить в статью — вообще неочевидный нюанс, я вот не знал, хотя при всей своей природной скромности не могу назвать себя начинающим джисером.
NaN и Ifninity — это не типы, а специальные значения типа Number. И это не только в ДжС, а во всех типа х всех языков, полностью поддерживающих стандарт представления чисел с плавающей запятой IEEE… не помню.
Похоже на осеннее обострение аллергии на javascript. Ну, ничего, к зиме пройдёт.

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


if (typeof window !== 'undefined') {
  // browser
} else {
  // Node.js
}
Советы из анабиоза. Это всегда было плохим стилем, а теперь так вообще не актуально. Спецификация ES5 запрещает переопределять undefined. Для колекций лучше использовать Map или Set которые есть во всех актуальных версиях браузеров (и без проблем полифилятся массивами), а не насиловать браузеры вызывая деоптимизацию. Магии нет, есть спецификация и преобразование типов достаточно с ними разобраться и больше не смеяться с видео javascript WTF. Например массив с join не конвертирует пустые значения в пустую строку, а тупо их пропускает. Уже IE10 устаревший браузер, прекращайте поддерживать IE8 и всю эту китайщину. Если прямо нужно — лучше использовать shim.
this это же ссылка, в js нет указателей, не?
Еще одна статья в духе JavaScript — говно.
Лучше бы написали как на этом «говне» писать качественный код.
!isNaN(Infinity) // => true – ожидалось что-то иное?
Как вообще тогда определять числа? Проверить их конечность!

Почему не !isNaN(value)?


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

Никакой экономии, так как у объекта по прежнему есть свойство на прототип, только оно равно null. То есть объект занимает столько же памяти.
А если используя Object.key() или аналог, то под хранение ключей создается массив строк, который требует памяти, а после попадает в GC как мусор.


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

Про это был большой разнос http://webreflection.blogspot.ru/2014/04/all-ie-objects-are-broken.html


arguments = Array.prototype.slice.call(arguments, 0); // Превращение в массив

В strict mode будет исключение – нельзя переопределять arguments.


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

Все верно, происходит destructuring, то есть неявно итерируется arguments с получением массива в качестве результата.


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

Совсем не факт. Но в комментариях уже писали, что наиболее правильный способ это new Function('return this')().

Sign up to leave a comment.

Articles