JS versus PHP battle
Сегодня будет небольшой баттл между PHP и JS. Баттл не по скорости и тому, кто круче, а просто пример того, как отличаются методы парсинга кода в интерпретаторах JS и PHP на примере одной простой задачки.
Дано:
var $a, $b;
$a = 1;
$b = 1;
console.log( $a + $a++ );
console.log( $b + $b + $b++ );
console.log( $a, $b );
и такой же код на PHP:
<?php
$a = 1;
$b = 1;
var_dump( $a + $a++ );
var_dump( $b + $b + $b++ );
var_dump( $a, $b );
Собственно простой вопрос: что будет выведено?
В целом задачка очевидна вроде бы. Зная как работают операторы и с какой стороны парсит парсер: слева направо или справа налево можно дать точный ответ. Думай как парсер, будь как парсер 🙂
Ответы
Нанчем с JS кода:
Чтение выражения идет слева направо. Изначально $a = 1. После вычисления выражения $a = 2. Итого получаем:
// 1 1 и после $a = 2
console.log( $a + $a++ ); // = 2
// 1 1 1 и после $b = 2
console.log( $b + $b + $b++ ); // = 3
console.log( $a, $b ); // 2 , 2
Это разные выражения и условие
($a + $a++) !== ($b + $b + $b++)
истинно. А теперь рассммотрим тот же код на PHP:
Прежде чем пойдем дальше, скажу что условие
($a + $a++) === ($b + $b + $b++)
для PHP будет истинно. Внезапно, да?!
(ссылка для проверки во всех актуальных версиях интерпретатора https://3v4l.org/1ss4E)
Ну давайте разбираться почему.
// 2 1 и после $a = 2
var_dump( $a + $a++ ); // = 3
Почему так? Сначала PHP смотрит на правую часть видит что там идет вызов $a и подставляет значение 1, но тут же продолжает вычисление, в результате которого происходит постфиксный инкремент. Теперь $a=2. Когда интерпретатор начинает вычислять левую часть $a уже равен 2 и у нас получается выражение 2 + 1
Что же происходит с $b?
1 1 1 и после $b = 2
$b + $b + $b++
В данном случае интерпретатор как бы “сгруппирует” выражения:
1 операция 2 операция
/ /
1 1 1 и после $b = 2
($b + $b) + $b++
Все дело в приоритете операторов. Если идете на собеседование, советую подтянуть знания почитав мануал http://php.net/manual/ru/language.operators.precedence.php
Если операторы имеют равный приоритет, то будут ли они выполняться справа налево или слева направо определяется их ассоциативностью. К примеру, “-” является лево-ассоциативным оператором. Следовательно 1–2–3 сгруппируется как (1–2) — 3 и пересчитается в -4. С другой стороны “=” — право-ассоциативный оператор, так что $a = $b = $c сгруппируется как $a = ($b = $c).
Неассоциативные операторы с одинаковым приоритетом не могут использоваться совместно. К примеру 1 < 2 > 1 не будет работать в PHP. Выражение 1 <= 1 == 1, с другой стороны, будет, поскольку == имеет более низкий приоритет чем <=.
И теперь, зная эту особенность, можно играть с группировками и префиксным инкрементом:
// 2 2 1
var_dump( $b + ($b + $b++) ); // 5
// 1 1 2
var_dump( $b + $b + ++$b ); // 4
// 1 1 2
var_dump( ($b + $b) + ++$b ); // 4
// 2 2 2
var_dump( $b + ($b + ++$b) ); // 6
В JS все будет более просто и предсказуемее: сначала подстановка, потом вычисление инкремента. В PHP же надо знать порядок группировки.
UPD
Alexander Karpan в комментарии к статье дал другой способ понять почему так происходит (спасибо за это!). ⇣⇣⇣ Если сдампить опкоды, то можно увидеть как происходят вычисления:
L8 : T7 = ADD CV1($b) CV1($b)
L9 : T8 = POST_INC CV1($b)
L10: T9 = ADD T7 T8
Итог
То что в JS не равно
var a = 1, b = 1;
(a + a++) !== (b + b + b++)
то в PHP еще как равно:
$a = 1; $b = 1;
($a + $a++) === ($b + $b + $b++)