eval()

Функція eval() обчислює код JavaScript, представлений у вигляді рядка.

Застереження: Виконання коду JavaScript з текстового рядка - це неймовірний ризик для безпеки. Зловмиснику занадто легко запустити який завгодно код, коли ви використовуєте eval(). Дивіться Ніколи не використовуйте eval()! нижче.

Синтаксис

eval(string)

Параметри

string
Рядок, що відображає вираз, інструкцію чи послідовність інструкцій JavaScript. Вираз може містити змінні та властивості існуючих об'єктів.

Значення, що повертається

Значення, отримане в результаті обчислення наданого коду. Якщо значення порожнє, повертається undefined.

Опис

Функція eval() є методом глобального об'єкта.

Аргументом функції eval() є рядок. Якщо у рядку представлений вираз, eval() обчислює цей вираз. Якщо у аргументі представлено одну чи більше інструкцій JavaScript, eval() обчислює ці інструкції. Не викликайте eval() для обчислення арифметичного виразу; JavaScript обчислює арифметичні вирази автоматично.

Якщо ви створили арифметичний вираз у вигляді рядка, ви можете скористатись eval(), щоб обчислити його пізніше. Припустимо, ви маєте змінну x. Ви можете відкласти обчислення виразу, що містить x, присвоївши рядкове значення виразу, скажімо, "3 * x + 2", змінній, а потім викликати eval() пізніше у скрипті.

Якщо аргумент eval() не є рядком, eval() повертає аргумент без змін. У наступному прикладі вказано конструктор String, і eval() повертає об'єкт String, а не обчислений рядок.

eval(new String('2 + 2')); // повертає об'єкт String, який містить "2 + 2"
eval('2 + 2');             // повертає 4

Ви можете обійти це обмеження загальним методом, використавши toString().

var expression = new String('2 + 2');
eval(expression.toString());            // повертає 4

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

function test() {
  var x = 2, y = 4;
  console.log(eval('x + y'));  // Прямий виклик, використовує локальну область видимості, результат 6
  var geval = eval; // еквівалентно виклику eval у глобальній області видимості
  console.log(geval('x + y')); // Непрямий виклик, використовує глобальну область видимості, викидає ReferenceError, бо `x` undefined
  (0, eval)('x + y'); // інший приклад непрямого виклику
}

Ніколи не використовуйте eval!

eval() - небезпечна функція, яка виконує переданий код з привілеями того, хто викликає функцію. Якщо ви запустите eval() з рядком, який міг бути уражений зловмисником, ви можете в результаті запустити шкідливий код на машині користувача з дозволами вашої сторінки / розширення. Ще важливіше, що код третьої сторони бачить область видимості, у якій було запущено eval(), що може призвести до можливих атак, способами, до яких схожий конструктор Function не сприйнятливий.

Також метод eval() повільніший, ніж його альтернативи, оскільки йому доводиться викликати інтерпретатор JS, в той час, як багато інших конструкцій оптимізуються сучасними рушіями JS.

Додатково, сучасні інтерпретатори JavaScript перетворюють код JavaScript на машинний код. Це означає, що будь-яке йменування змінних знищується. Тому будь-яке використання eval змусить переглядач виконувати довгі, затратні пошуки імен змінних, щоб зрозуміти, де ця змінна існує у машинному коді, та присвоїти їй значення. До того ж, до змінної можуть бути внесені нові зміни через eval(), наприклад, зміна типу цієї змінної, змушуючи переглядач переобчислювати весь згенерований машинний код, щоб надолужити зміни. Однак, на щастя, існує дуже гарна альтернатива eval: просто використовуйте window.Function. Приклад того, як можна перетворити код з використанням шкідливого eval() на код з використанням Function(), дивіться нижче.

Поганий код з eval:

function looseJsonParse(obj){
    return eval(obj);
}
console.log(looseJsonParse(
   "{a:(4-1), b:function(){}, c:new Date()}"
))

Покращений код без eval:

function looseJsonParse(obj){
    return Function('"use strict";return (' + obj + ')')();
}
console.log(looseJsonParse(
   "{a:(4-1), b:function(){}, c:new Date()}"
))

Порівнюючи ці два фрагменти коду, можна подумати, що вони працюють однаково, але не забувайте: код з eval набагато повільніший. Зверніть увагу на c: new Date() у обчислюваному об'єкті. У функції без eval об'єкт обчислюється у глобальній області видимості, тому переглядач може спокійно припускати, що Date посилається на window.Date, а не на локальну змінну на ім'я Date. Але у коді, що використовує eval(), переглядач не може цього припускати, бо що, як ваш код виглядає наступним чином:

function Date(n){
    return ["Понеділок","Вівторок","Середа","Четвер","П'ятниця","Субота","Неділя"][n%7 || 0];
}
function looseJsonParse(obj){
    return eval(obj);
}
console.log(looseJsonParse(
   "{a:(4-1), b:function(){}, c:new Date()}"
))

Таким чином, у версії з eval() переглядач змушений запускати затратний пошук, щоб перевірити, чи немає десь локальних змінних на ім'я Date(). Це страшенно неефективно, у порівнянні з Function().

У цих обставинах, що як вам дійсно потрібно, щоб функція Date могла викликатися з коду всередині Function()? Чи доведеться відступити та повернутися до eval()? Ні! Нізащо. Натомість, спробуйте наступний підхід.

function Date(n){
    return ["Понеділок","Вівторок","Середа","Четвер","П'ятниця","Субота","Неділя"][n%7 || 0];
}
function runCodeWithDateFunction(obj){
    return Function('"use strict";return (' + obj + ')')()(
        Date
    );
}
console.log(runCodeWithDateFunction(
   "function(Date){ return Date(5) }"
))

Наведений код може виглядати неефективно повільним через потрійну вкладеність функції, але проаналізуємо переваги наведеного вище методу:

  • Він дозволяє мініфікувати код у рядку, переданому до runCodeWithDateFunction.
  • Затрати на виклик функції мінімальні, що разом з набагато меншим за розміром кодом є однозначною перевагою
  • Function() легше дозволяє покращити продуктивність вашого коду через "use strict";
  • Код не використовує eval(), що робить його на порядки швидшим.

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

console.log(Function('"use strict";return(function(a){return a(5)})')()(function(a){
return"Понеділок Вівторок Середа Четвер П'ятниця Субота Неділя".split(" ")[a%7||0]}));

Існують також інші безпечніші (та швидші!) альтернативи eval() чи Function() для типових випадків використання.

Звернення до властивостей

Не слід використовувати eval() для перетворення імен властивостей на властивості. Розглянемо наступний приклад, де властивість об'єкта, до якої звертаються, невідома до початку виконання коду. Це можна зробити через eval:

var obj = { a: 20, b: 30 };
var propName = getPropName();  // повертає "a" або "b"

eval( 'var result = obj.' + propName );

Однак, метод eval() тут не обов'язковий. Насправді, його використання тут не рекомендоване. Натомість, скористайтесь зверненням до властивостей, це набагато швидше та безпечніше:

var obj = { a: 20, b: 30 };
var propName = getPropName();  // повертає "a" або "b"
var result = obj[ propName ];  //  obj[ "a" ] - це те саме, що й obj.a

Ви навіть можете скористатись цим методом, щоб звернутись до вкладених властивостей. З eval() це виглядало б наступним чином:

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath();  // повертає, наприклад, "a.b.c"

eval( 'var result = obj.' + propPath );

Уникнути eval() тут можна, розбивши шлях до властивості на масив та пройшовши у циклі через властивості:

function getDescendantProp(obj, desc) {
  var arr = desc.split('.');
  while (arr.length) {
    obj = obj[arr.shift()];
  }
  return obj;
}

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath();  // повертає, наприклад, "a.b.c"
var result = getDescendantProp(obj, propPath);

Призначення властивості таким чином працює схоже:

function setDescendantProp(obj, desc, value) {
  var arr = desc.split('.');
  while (arr.length > 1) {
    obj = obj[arr.shift()];
  }
  return obj[arr[0]] = value;
}

var obj = {a: {b: {c: 0}}};
var propPath = getPropPath();  // повертає, наприклад, "a.b.c"
var result = setDescendantProp(obj, propPath, 1);  // obj.a.b.c тепер дорівнюватиме 1

Використовуйте функції замість обчислення фрагментів коду

JavaScript має функції першого класу, це означає, що ви можете передавати функції як аргументи до інших API, зберігати їх у змінних та властивостях об'єктів, і так далі. Багато API об'єктів DOM створені з метою такого використання, тому ви можете (і маєте) написати:

// замість setTimeout(" ... ", 1000) використовуйте:
setTimeout(function() { ... }, 1000); 

// замість elt.setAttribute("onclick", "...") використовуйте:
elt.addEventListener('click', function() { ... } , false); 

Замикання також корисні як спосіб створення параметризованих функцій без поєднання рядків.

Розбір JSON (перетворення рядків на об'єкти JavaScript)

Якщо рядок, для якого ви викликаєте eval(), містить дані (наприклад, масив: "[1, 2, 3]"), як протилежність коду, вам слід розглянути перехід на JSON, це дозволить рядку використовувати підмножину синтаксису JavaScript для представлення даних. Дивіться також Завантаження JSON та JavaScript у розширеннях.

Зауважте, що, оскільки синтаксис JSON є обмеженим, у порівнянні з синтаксисом JavaScript, багато з чинних літералів JavaScript не розбиратимуться як JSON. Наприклад, прикінцеві коми не дозволені у JSON, а імена властивостей (ключі) у об'єкті повинні бути заключені у лапки. Обов'язково використовуйте серіалізатор JSON для створення рядків, які пізніше будуть розібрані як JSON.

Передавайте дані замість коду

Наприклад, розширення, створене для збирання вмісту веб-сторінок, може мати правила збирання, визначені у XPath, замість коду JavaScript.

Запускайте код з обмеженими привілеями

Якщо ви мусите запускати код, розгляньте варіант запуску з обмеженими привілеями. Ця порада стосується здебільшого розширень та XUL-застосунків, які можуть використовувати для цього Components.utils.evalInSandbox.

Приклади

Використання eval

У наступному коді обидві інструкції, які містять eval(), повертають 42. Перша обчислює рядок "x + y + 1"; друга обчислює рядок "42".

var x = 2;
var y = 39;
var z = '42';
eval('x + y + 1'); // повертає 42
eval(z);           // повертає 42 

Використання eval для обчислення рядка інструкцій JavaScript

Наступний приклад використовує eval(), щоб обчислити рядок str. Цей рядок складається з інструкцій JavaScript, які виводять повідомлення та присвоюють змінній z значення 42, якщо x дорівнює п'яти, інакше присвоюють z значення 0. Коли виконується друга інструкція, eval() спричинить виконання цих інструкцій, а також обчислить набір інструкцій та поверне значення, що було присвоєне z.

var x = 5;
var str = "if (x == 5) {console.log('z дорівнює 42'); z = 42;} else z = 0;";

console.log('z дорівнює ', eval(str));

Якщо ви визначаєте декілька значень, то повертається останнє.

var x = 5;
var str = "if (x == 5) {console.log('z дорівнює 42'); z = 42; x = 420; } else z = 0;"; 

console.log('x дорівнює ', eval(str)); // z дорівнює 42  x дорівнює 420 

Останній вираз обчислюється

eval() повертає значення останнього обчисленого виразу.

var str = 'if ( a ) { 1 + 1; } else { 1 + 2; }';
var a = true;
var b = eval(str);  // повертає 2
 
console.log('b дорівнює : ' + b);

a = false;
b = eval(str);  // повертає 3

console.log('b дорівнює : ' + b);

eval як функція визначення рядка, потребує "(" та ")" на початку та в кінці

var fctStr1 = 'function a() {}'
var fctStr2 = '(function a() {})'
var fct1 = eval(fctStr1)  // повертає undefined
var fct2 = eval(fctStr2)  // повертає функцію

Специфікації

Специфікація Статус Коментар
ECMAScript 1st Edition (ECMA-262) Standard Початкове визначення.
ECMAScript 5.1 (ECMA-262)
The definition of 'eval' in that specification.
Standard
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'eval' in that specification.
Standard
ECMAScript Latest Draft (ECMA-262)
The definition of 'eval' in that specification.
Draft

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
evalChrome Full support 1Edge Full support 12Firefox Full support 1IE Full support 3Opera Full support YesSafari Full support YesWebView Android Full support 1Chrome Android Full support 18Firefox Android Full support 4Opera Android Full support YesSafari iOS Full support YesSamsung Internet Android Full support 1.0nodejs Full support Yes

Legend

Full support  
Full support

Примітки щодо Firefox

  • Історично eval() мав необов'язковий другий аргумент, що вказував об'єкт, в контексті якого мало виконуватись обчислення. Цей аргумент був нестандартним, і був остаточно прибраний з Firefox 4. Дивіться bug 531675.

Див. також