Є ще один спосіб створити функцію. Він рідко використовується, але йому немає альтернативи.
Синтаксис
Синтаксис для створення функції:
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]]
, що посилається на глобальне лексичне середовище, а не зовнішнє. Отже, вони не можуть використовувати зовнішні змінні. Але це насправді добре, тому що це застраховує нас від помилок. Передача параметрів явно є набагато кращим методом, архітектурно і не викликає проблем з мініфікаторами.