Предварительное увеличение в Javascript

Я только что столкнулся с "особенностью" в Javascript относительно предварительных приращений. На всех других языках, которые я использовал, это происходит, как я думал. Например. в С++:

#include <iostream>

int main()
{
    int i = 0;

    i += ++i;

    std::cout << i << std::endl; // Outputs 2.
}

Итак, ++i не делает копию переменной, поэтому выход равен 2.

То же самое в PHP:

<?php

$i = 0;

$i += ++$i;

echo $i; // Outputs 2.

Однако в Javascript:

var i = 0;

i += ++i;

console.log(i); // Outputs 1.

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

Ответ 1

Из стандарта EcmaScript:

11.4.4 Оператор приращения префикса

Вывод UnaryExpression: ++ UnaryExpression оценивается следующим образом:

  • Пусть expr является результатом вычисления UnaryExpression.
  • Выбросить исключение SyntaxError, если все условия верны:
    • Тип (expr) имеет значение Reference is true
    • IsStrictReference (expr) истинно
    • Тип (GetBase (expr)) - это запись среды.
    • GetReferencedName (expr) является либо "eval", либо "arguments"
  • Пусть oldValue будет ToNumber (GetValue (expr)).
  • Пусть newValue является результатом добавления значения от 1 до oldValue, используя те же правила, что и для оператора + (см. 11.6.3).
  • Вызов PutValue (expr, newValue).
  • Возвращает newValue.

и

11.13.2 Назначение соединения (op =)

Производственное присвоение Выражение: LeftHandSideExpression AssignmentOperator AssignmentExpression, где AssignmentOperator является @= и @представляет один из операторов, указанных выше, оценивается следующим образом:

  • Пусть lref является результатом оценки LeftHandSideExpression.
  • Пусть lval - GetValue (lref).
  • Пусть rref является результатом вычисления AssignmentExpression.
  • Пусть rval - GetValue (rref).
  • Пусть r - результат применения оператора @к lval и rval.
  • Выбросить исключение SyntaxError, если все условия верны:
    • Тип (lref) имеет значение true.
    • IsStrictReference (lref) истинно
    • Тип (GetBase (lref)) - это запись среды.
    • GetReferencedName (lref) является либо "eval", либо "arguments"
  • Вызов PutValue (lref, r)

Таким образом, var i = 0; i += ++i:

i = 0;
lvalue = value(i), which is 0;
rvalue = value(++i), which is: increment i, then value of i (1);
thus, rvalue = 1;
i = lvalue (0) + rvalue (1), which is 1.

Полностью соответствует спецификации.

Однако в С++ это определенно определяется как поведение undefined, поэтому на другом компиляторе вы также можете получить 1. Или 99. Или это может привести к выходу компьютера из строя. Все они будут стандартными компиляторами. Таким образом, большинство людей будут рекомендовать вам использовать переменную pre/post-incremented только в инструкции.

Ответ 2

Я считаю, что это потому, что javascript видит var i = 0; i += ++i; как это:

var i = 0;
++i = 1;
i += 1;

Это потому, что ++i превращает я в 1 перед выполнением +=, поэтому он 0 + 1 = 1

На самом деле, чем больше я думаю об этом, зачем ему делать что-нибудь еще?

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

  • do ++ я сначала и сделайте я = я + 1, где я начинается с 0 и приравнивается до 1

или

  • сделайте сначала + = и сделайте его я = 0 + (++ i), который также будет равен 1