Добавление объектов с номерами [дубликат]

Вместо того, чтобы бросать код на вас, есть два понятия, которые являются ключом к пониманию того, как JS обрабатывает обратные вызовы и асинхронность. (это даже слово?)

Модель цикла события и параллелизма

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

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

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

Как только он получает сообщение для запуска чего-то, он добавляет его в очередь. Очередь - это список вещей, которые ждут выполнения (например, ваш запрос AJAX). Представьте себе это так:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

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

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

. Так что все, что foobarFunc должно выполнить (в нашем случае anotherFunction), будет вставлено в стек. исполняемый, а затем забытый - цикл события затем переместится на следующую вещь в очереди (или прослушивает сообщения)

. Главное здесь - порядок выполнения. Это

КОГДА что-то будет запущено

Когда вы совершаете вызов с использованием AJAX для внешней стороны или выполняете любой асинхронный код (например, setTimeout), Javascript зависит от ответ, прежде чем он сможет продолжить.

Большой вопрос, когда он получит ответ? Ответ в том, что мы не знаем, поэтому цикл событий ждет, когда это сообщение скажет: «Эй, забери меня». Если JS просто ждал этого сообщения синхронно, ваше приложение замерзнет, ​​и оно сосать. Таким образом, JS продолжает выполнение следующего элемента в очереди, ожидая, пока сообщение не будет добавлено обратно в очередь.

Вот почему с асинхронной функциональностью мы используем вещи, называемые обратными вызовами. Это похоже на обещание буквально. Как и в I , обещание что-то вернуть в какой-то момент jQuery использует специальные обратные вызовы, называемые deffered.done deffered.fail и deffered.always (среди других). Вы можете увидеть их все здесь

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

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

function foo(bla) {
  console.log(bla)
}

, поэтому большую часть времени (но не всегда) вы пройдете foo не foo()

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

708
задан Esailija 13 August 2012 в 21:23
поделиться

5 ответов

Вот список объяснений результатов, которые вы видите (и должен их видеть). Ссылки, которые я использую, из стандарта ECMA-262 .

  1. [] + []

    При использовании оператора сложения, как слева, так и слева и правые операнды сначала преобразуются в примитивы ( §11.6.1 ). Согласно §9.1 преобразование объекта (в данном случае массива) в примитив возвращает значение по умолчанию, которое для объектов с допустимым toString() методом является результатом вызова object.toString() ( §8.12.8 ). Для массивов это то же самое, что и вызов array.join() ( §15.4.4.2 ). Объединение пустого массива приводит к пустой строке, поэтому шаг # 7 оператора сложения возвращает конкатенацию двух пустых строк, которая является пустой строкой.
  2. [] + {}

    Похожие к [] + [] оба операнда сначала преобразуются в примитивы. Для «Объектных объектов» (§15.2) это снова является результатом вызова object.toString(), который для непустых не неопределенных объектов является "[object Object]" ( §15.2.4.2 ).
  3. {} + []

    Здесь {} не анализируется как объект, а вместо этого как пустой блок ( §12.1 , по крайней мере, до тех пор, пока вы не заставляете это выражение быть выражением, но об этом позже). Возвращаемое значение пустых блоков пуст, поэтому результат этого оператора совпадает с +[]. Унарный + оператор ( §11.4.6 ) возвращает ToNumber(ToPrimitive(operand)). Как мы уже знаем, ToPrimitive([]) является пустой строкой, и согласно §9.3.1 , ToNumber("") равен 0.
  4. {} + {}

    Как и в предыдущем случае, первый {} анализируется как блок с пустым возвращаемым значением. Опять же, +{} совпадает с ToNumber(ToPrimitive({})), а ToPrimitive({}) - "[object Object]" (см. [] + {}). Поэтому, чтобы получить результат +{}, мы должны применить ToNumber к строке "[object Object]". Следуя шагам из §9.3.1 , мы получаем NaN в результате: если грамматика не может интерпретировать строку как расширение StringNumericLiteral , то результат ToNumber - NaN.
  5. Array(16).join("wat" - 1)

    Согласно §15.4.1.1 и §15.4.2.2 , Array(16) создает новый массив с длиной 16. Чтобы получить значение аргумента для соединения, §11.6.2 шаги # 5 и # 6 показывают, что нам нужно преобразовать оба операнда в число, используя ToNumber. ToNumber(1) является просто 1 ( §9.3 ), тогда как ToNumber("wat") снова NaN в соответствии с §9.3.1 . Следуя шагу 7 из §11.6.2 , §11.6.3 диктует, что если любой из операндов равен NaN, результатом является NaN. Поэтому аргументом Array(16).join является NaN. Следуя § 15.4.4.5 (Array.prototype.join), мы должны называть ToString аргументом, который является "NaN" ( §9.8.1 ): если m является NaN, возвращает строку "NaN". Следуя шагу 10 из §15.4.4.5 , мы получаем 15 повторений конкатенации "NaN" и пустой строки, что равно результату, который вы видите. При использовании параметра "wat" + 1 вместо "wat" - 1 в качестве аргумента оператор сложения преобразует 1 в строку вместо преобразования "wat" в число, поэтому он эффективно вызывает Array(16).join("wat1").

Что касается того, почему вы видите разные результаты для случая {} + []: при использовании в качестве аргумента функции вы вынуждаете оператор быть ExpressionStatement , что делает невозможным синтаксический анализ {} как пустой блок, поэтому вместо этого он анализируется как пустой литерал объекта.

1425
ответ дан Joey 5 September 2018 в 15:13
поделиться

Чтобы подкрепить то, что было ранее ранее.

Основная причина этого поведения частично объясняется слабо типизированным характером JavaScript. Например, выражение 1 + «2» неоднозначно, поскольку существуют две возможные интерпретации, основанные на типах операндов (int, string) и (int int):

  • Пользователь намеревается объединить две строки , результат: «12»
  • Пользователь намеревается добавить два числа, результат: 3

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

Алгоритм сложения

  1. Поверенные операнды к примитивным значениям

Примитивы JavaScript - это строка, число, значение null, undefined и boolean (Symbol скоро появится в ES6). Любое другое значение - это объект (например, массивы, функции и объекты). Процесс принуждения для преобразования объектов в примитивные значения описывается таким образом:

  • Если примитивное значение возвращается при вызове object.valueOf (), тогда возвращайте это значение, в противном случае продолжайте
  • Если примитивное значение возвращается при вызове object.toString (), тогда возвращайте это значение, в противном случае продолжайте
  • Бросьте TypeError

Примечание: для значений даты , порядок должен вызывать toString до valueOf.

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

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

+-----------------+-------------------+---------------+
| Primitive Value |   String value    | Numeric value |
+-----------------+-------------------+---------------+
| null            | “null”            | 0             |
| undefined       | “undefined”       | NaN           |
| true            | “true”            | 1             |
| false           | “false”           | 0             |
| 123             | “123”             | 123           |
| []              | “”                | 0             |
| {}              | “[object Object]” | NaN           |
+-----------------+-------------------+---------------+

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

Использование таким образом 1 + «2» даст «12», потому что любое добавление, включающее строку, всегда будет по умолчанию для конкатенации строк.

Вы можете прочитать больше примеров в в этом сообщении в блоге (отказ от ответственности я написал).

0
ответ дан AbdulFattah Popoola 5 September 2018 в 15:13
поделиться

I второе решение Ventero. Если вы хотите, вы можете подробнее рассказать о том, как + преобразует свои операнды.

Первый шаг (§9.1): конвертировать оба операнда в примитивы (примитивные значения: undefined, null, booleans, numbers, strings, все остальные значения - объекты, включая массивы и функции). Если операнд уже примитивен, все готово. Если нет, это объект obj, и выполняются следующие шаги:

  1. Вызов obj.valueOf(). Если он возвращает примитив, вы закончите. Прямые экземпляры Object и массивы возвращаются, поэтому вы еще не закончили.
  2. Вызов obj.toString(). Если он возвращает примитив, вы закончите. {} и [] оба возвращают строку, поэтому вы закончили.
  3. В противном случае введите TypeError.

Для дат, шагов 1 и 2 меняются местами. Вы можете наблюдать поведение преобразования следующим образом:

var obj = {
    valueOf: function () {
        console.log("valueOf");
        return {}; // not a primitive
    },
    toString: function () {
        console.log("toString");
        return {}; // not a primitive
    }
}

Взаимодействие (Number() сначала преобразуется в примитив, а затем в число):

> Number(obj)
valueOf
toString
TypeError: Cannot convert object to primitive value

Второй шаг (§11.6.1 ): Если один из операндов является строкой, другой операнд также преобразуется в строку, и результат получается путем объединения двух строк. В противном случае оба операнда преобразуются в числа, и результат получается путем их добавления.

Более подробное объяснение процесса преобразования: « Что такое {} + {} в JavaScript? «

16
ответ дан Axel Rauschmayer 5 September 2018 в 15:13
поделиться

Это скорее комментарий, чем ответ, но по какой-то причине я не могу прокомментировать ваш вопрос. Я хотел исправить код JSFiddle. Тем не менее, я опубликовал это в Hacker News, и кто-то предложил мне его перепечатать.

Проблема в коде JSFiddle заключается в том, что ({}) (открытие скобок внутри круглых скобок) не совпадает с {} (открытие скобок как начало строки кода). Поэтому, когда вы вводите out({} + []), вы вынуждаете {} быть тем, чего нет, когда вы набираете {} + []. Это часть общей «ваттности» Javascript.

Основная идея заключалась в простом JavaScript, который хотел бы разрешить обе эти формы:

if (u)
    v;

if (x) {
    y;
    z;
}

Чтобы сделать это, два были сделаны интерпретации открывающей скобки: 1. не требуется и 2. он может появиться где угодно .

Это был неправильный ход. Реальный код не имеет открывающей скобки, появляющейся в середине нигде, и реальный код также имеет тенденцию быть более хрупким, когда он использует первую форму, а не вторую. (Примерно раз в другой месяц на моей последней работе меня вызывали на стол коллеги, когда их изменения в моем коде не работали, и проблема заключалась в том, что они добавили строку в «if» без добавления фигурных скобки. В конце концов я просто принял привычку, что фигурные скобки всегда требуются, даже когда вы пишете только одну строку.)

К счастью, во многих случаях eval () будет воспроизводить полную ваттность JavaScript , Код JSFiddle должен читать:

function out(code) {
    function format(x) {
        return typeof x === "string" ?
            JSON.stringify(x) : x;
    }   
    document.writeln('>>> ' + code);
    document.writeln(format(eval(code)));
}
document.writeln("<pre>");
out('[] + []');
out('[] + {}');
out('{} + []');
out('{} + {}');
out('Array(16).join("wat" + 1)');
out('Array(16).join("wat - 1")');
out('Array(16).join("wat" - 1) + " Batman!"');
document.writeln("</pre>");

[Также это первый раз, когда я написал document.writeln за многие много лет, и я чувствую себя немного грязным, пишу что-нибудь, связанное с document.writeln () и eval ().]

28
ответ дан CR Drost 5 September 2018 в 15:13
поделиться

Мы можем ссылаться на спецификацию, и это замечательно и точно, но большинство случаев также можно объяснить более понятным образом со следующими утверждениями:

  • + и - работают только с примитивными значениями. Более конкретно + (дополнение) работает с любыми строками или числами, а + (унарный) и - (вычитание и унарный) работает только с числами.
  • Все нативные функции или операторы, которые ожидают примитивное значение как аргумент, сначала преобразует этот аргумент в нужный примитивный тип. Это делается с помощью valueOf или toString, которые доступны для любого объекта. Вот почему такие функции или операторы не вызывают ошибок при вызове на объекты.

Таким образом, мы можем сказать, что:

  • [] + [] как String([]) + String([]), который аналогичен '' + ''. Я упомянул выше, что + (дополнение) также справедливо для чисел, но в JavaScript нет допустимого числа в массиве, поэтому вместо этого используется добавление строк.
  • [] + {} такое же, как String([]) + String({}), который аналогичен '' + '[object Object]'
  • {} + []. Это заслуживает большего объяснения (см. Ответ Вентеро). В этом случае фигурные скобки обрабатываются не как объект, а как пустой блок, поэтому он оказывается таким же, как +[]. Unary + работает только с числами, поэтому реализация пытается получить номер из []. Сначала он пытается valueOf, который в случае массивов возвращает один и тот же объект, поэтому он пытается использовать последнее средство: преобразование результата toString в число. Мы можем записать его как +Number(String([])), который аналогичен +Number(''), который аналогичен +0.
  • Array(16).join("wat" - 1) вычитание - работает только с числами, поэтому это то же самое, что: Array(16).join(Number("wat") - 1), поскольку "wat" не может быть преобразовано в действительное число. Мы получаем NaN, и любая арифметическая операция на NaN получается с NaN, поэтому мы имеем: Array(16).join(NaN).
13
ответ дан Peter Mortensen 5 September 2018 в 15:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: