пятница, 1 ноября 2024 г.

Javascript: ниндзя-код в javascript с помощью "hoisting"

Декларация языка javascript позволяет сделать очень много комбинаций из разнообразных выражений и по мнению компилятора/интерпретатора все они будут вполне законны и будут выполняться без ошибок.

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

Вот этот код (страничка в браузере):

<body>
<script>
window.myproj = {};
window.myproj.func1 = () => {
  alert(getFirstName());
}
</script>
<script>
window.myproj.func2 = () => {
  myproj.func1(
    getFirstName = () => { return "Alex"; },
  );
}
</script>
<script>
window.myproj.func2();
</script>
</body>

Можно ли по коду сказать что покажет в диалоге func2?
Я не смог.

(пример конечно же сильно упрощен, но он хорошо демонстрирует реальный код)

Заранее, перед просмотром этого кода я уже знал, что он работает без ошибок и показывает текст "Alex". И рассчитывал на быстрое "ревью".

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

window.myproj.func1 = () => {
  alert(getFirstName());
}

Но аргумента не было. Вместо него была "просто переменная". И по коду мне было совершенно не понятно откуда она появилась в этом блоке "<script>", почему при выполнении не было ошибки навроде "unknown variable" и как переменная смогла возвращать правильное значение.

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

И уже под отладкой действительно узнал кое-что новое.

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

"Присвоение переменной" - это одно из таких выражений (значение переменной компилятор передаст дальше как значение для аргумента функции)

Присвоение переменной без явного объявления сделает новую переменную где-то на высоком уровне (это называется "hoisting")

В моем коде высоким уровнем будет window.

Свойства window доступны без префикса 'window.getFirstName' и можно писать сразу 'getFirstName'.

Поэтому переменная getFirstName возвращает верное значение и показывается правильный текст.

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

Я сначала это так и читал как "именованное присвоение значений в конкретные аргументы функции". В cs есть такое и в js похожий синтаксис. Мои глаза уже привыкли, что если код разбит на строки как в моем примере и на месте для аргумента функции есть присвоения, значит это присвоение в аргументы. Я даже не вчитывался в символы в этом коде.

И долго не видел, что тут нет никакого "именованное присвоение" и не мог понять без отладки как этот код вообще успешно работает.

В моем проекте по коду должно быть понятно как он работает и для понимания не должны требоваться какие-то специальные знания (например "hoisting"). Технические ньюансы в коде должны быть очевидны и я переделал код с "hoistingна явную передачу аргументов:

<body>
<script>
window.myproj= {};
window.myproj.func1 = ({ getFirstName }) => {
  alert(getFirstName());
}
</script>
<script>
window.myproj.func2 = () => {
  myproj.func1({
    getFirstName: () => { return "Alex"; },
  });
}
</script>
<script>
  window.myproj.func2();
</script>
</body>

В этом варианте в коде написано именно то, что видно при беглом просмотре: передача аргументов в функцию и внутри функции обращение к своим аргументам.

Комментариев нет:

Отправить комментарий