14 вересня 2021 р.

Синтаксис "new Function"

Є ще один спосіб створити функцію. Він рідко використовується, але йому немає альтернативи.

Синтаксис

Синтаксис для створення функції:

let func = new Function ([arg1, arg2, ...argN], functionBody);

Функція створюється з аргументами arg1...argN і з даним functionBody.

Легше зрозуміти, дивлячись на приклад. Ось функція з двома аргументами:

let sum = new Function('a', 'b', 'return a + b');

alert( sum(1, 2) ); // 3

А це функція без аргументів, лише з тілом функції:

let sayHi = new Function('alert("Привіт")');

sayHi(); // Привіт

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

Всі попередні оголошення вимагали нас, програмістів, написати код функції у скрипті.

Але new Function дозволяє перетворити будь-який рядок у функцію. Наприклад, ми можемо отримати нову функцію з сервера, а потім виконати її:

let str = ... код, який отриманий від сервера динамічно ...

let func = new Function(str);
func();

Це використовується в дуже специфічних випадках, як, коли ми отримуємо код з сервера, або для динамічної компіляції функцію з шаблону, у складних вебдодатках.

Замикання

Зазвичай функція пам’ятає, де вона народилася в спеціальній властивості [[Environment]]. Вона посилається на лексичне середовище, звідки функція була створена (ми розглянули це в розділі Область видимості змінної, замикання).

Але коли функція створюється за допомогою new Function, її [[Environment]] встановлюється так, щоб вказати глобальне лексичне середовище, а не поточне.

Отже, така функція не має доступу до зовнішніх змінних, тільки до глобальних.

function getFunc() {
  let value = "тест";

  let func = new Function('alert(value)');

  return func;
}

getFunc()(); // Помилка: value не визначено

Порівняйте це зі звичайною поведінкою:

function getFunc() {
  let value = "тест";

  let func = function() { alert(value); };

  return func;
}

getFunc()(); // "тест", з лексичного середовища функції getFunc

Ця особлива особливість new Function виглядає дивно, але на практиці виявляється дуже корисною.

Уявіть собі, що ми повинні створити функцію з рядка. Код цієї функції не відомий під час написання сценарію (ось чому ми не використовуємо звичайні функції), але буде відомий в процесі виконання. Ми можемо отримати його з сервера або з іншого джерела.

Наша нова функція повинна взаємодіяти з основним скриптом.

Що робити, щоб вона могла отримати доступ до зовнішніх змінних?

Проблема полягає в тому, що до відправки JavaScript-коду до сервера реального робочого проєкту, він стискається за допомогою мініфікатора – це спеціальна програма, яка стискає код, видаляючи додаткові коментарі, пробіли та, що важливо, перейменовує локальні змінні короткими іменами.

Наприклад, якщо функція має let userName, то мініфікатор замінює цей код на let a (або іншу букву, якщо ця зайнята) і робить це всюди. Це, як правило, безпечна річ, тому що змінна локальна, нічого поза межами функції не може отримати доступ до неї. І всередині функції, мініфікатор замінює кожне згадування про цю змінну. Мініфікатори розумні, вони аналізують структуру коду, тому вони нічого не порушують. Вони не просто здійснюють “примітивний” пошук-заміну.

Отже, якщо new Function мала доступ до зовнішніх змінних, вона не зможе знайти перейменовану змінну username.

** Якби new Function мала доступ до зовнішніх змінних, то вона б мала проблеми з мініфікаторами.**

Крім того, такий код буде архітектурно поганим і схильним до помилок.

Щоб передати щось до функції, створеної як new Function, ми повинні використовувати її аргументи.

Підсумки

Синтаксис:

let func = new Function ([arg1, arg2, ...argN], functionBody);

З історичних причин аргументи також можуть бути надані як список, розділений комами.

Ці три оголошення означають одне і те ж:

new Function('a', 'b', 'return a + b'); // основний синтаксис
new Function('a,b', 'return a + b'); // через кому
new Function('a , b', 'return a + b'); // через кому з пробілами

Функції, створені з new Function, мають [[Environment]], що посилається на глобальне лексичне середовище, а не зовнішнє. Отже, вони не можуть використовувати зовнішні змінні. Але це насправді добре, тому що це застраховує нас від помилок. Передача параметрів явно є набагато кращим методом, архітектурно і не викликає проблем з мініфікаторами.

Навчальна карта