Spec-Zone.ru › JavaScript

eval()

Базовый уровень Широко доступен

Эта функция хорошо зарекомендовала себя и работает на многих устройствах и версиях браузеров. Она доступна в браузерах с июля 2015 года.

Внимание: Аргумент, передаваемый этой функции, динамически парсится и выполняется как JavaScript. API, подобные этому, известны как инъекционные точки и потенциально являются вектором для атак межсайтового скриптинга (XSS).

Вы можете снизить этот риск, всегда передавая объекты TrustedScript вместо строк и принудительно применяя доверенные типы.

См. Соображения безопасности для получения дополнительной информации.

Функция eval() вычисляет JavaScript-код, представленный в виде строки, и возвращает его итоговое значение. Источник парсится как скрипт.

Попробуйте

console.log(eval("2 + 2"));
// Expected output: 4

console.log(eval(new String("2 + 2")));
// Expected output: 2 + 2

console.log(eval("2 + 2") === eval("4"));
// Expected output: true

console.log(eval("2 + 2") === eval(new String("2 + 2")));
// Expected output: false

Синтаксис

eval(script)

Параметры

script
Экземпляр TrustedScript или строка, представляющая JavaScript-выражение, инструкцию или последовательность инструкций. Выражение может включать переменные и свойства существующих объектов. Оно будет парситься как скрипт, поэтому объявления import (которые могут существовать только в модулях) не допускаются.

Возвращаемое значение

Итоговое значение вычисления данного кода. Если итоговое значение пустое, возвращается undefined. Если script не является TrustedScript или строковым примитивом, eval() возвращает аргумент без изменений.

Исключения

SyntaxError
Параметр script не может быть разобран как скрипт.
TypeError
script является строкой, когда Trusted Types принудительно применяются с помощью CSP и политика по умолчанию не определена.

Метод также генерирует любое исключение, которое возникает при вычислении кода.

Описание

eval() является свойством объекта global.

Аргументом функции eval() является строка. Она вычисляет исходную строку как тело скрипта, что означает, что допускаются как инструкции, так и выражения. Она возвращает итоговое значение кода. Для выражений — это значение, к которому вычисляется выражение. Многие инструкции и объявления также имеют итоговые значения, но результат может быть неожиданным (например, итоговым значением присваивания является присвоенное значение, а итоговым значением let — undefined), поэтому рекомендуется не полагаться на итоговые значения инструкций.

В строгом режиме объявление переменной с именем eval или переприсваивание eval является SyntaxError.

"use strict";

const eval = 1; // SyntaxError: Unexpected eval or arguments in strict mode

Если аргумент eval() не является TrustedScript или строкой, eval() возвращает аргумент без изменений. В следующем примере передача объекта String вместо примитива приводит к тому, что eval() возвращает объект String вместо вычисления строки.

eval(new String("2 + 2")); // returns a String object containing "2 + 2"
eval("2 + 2"); // returns 4

Чтобы обойти эту проблему универсальным способом, вы можете преобразовать аргумент в строку самостоятельно перед передачей его в eval().

const expression = new String("2 + 2");
eval(String(expression)); // returns 4

Прямой и косвенный eval

Существуют два режима вызовов eval(): *прямой* eval и *косвенный* eval. Прямой eval, как следует из названия, относится к *прямому* вызову глобальной функции eval с eval(...). Все остальное, включая вызов через псевдоним переменной, через доступ к члену или другое выражение, или через оператор опциональной цепочки ?., является косвенным.

// Direct call
eval("x + y");

// Indirect call using the comma operator to return eval
(0, eval)("x + y");

// Indirect call through optional chaining
eval?.("x + y");

// Indirect call using a variable to store and return eval
const myEval = eval;
myEval("x + y");

// Indirect call through member access
const obj = { eval };
obj.eval("x + y");

Косвенный eval можно рассматривать так, как если бы код вычислялся внутри отдельного тега <script>. Это означает:

  • Косвенный eval работает в глобальной области видимости, а не в локальной, и код, который вычисляется, не имеет доступа к локальным переменным в области видимости, где он вызывается.

    function test() {
      const x = 2;
      const y = 4;
      // Direct call, uses local scope
      console.log(eval("x + y")); // Result is 6
      // Indirect call, uses global scope
      console.log(eval?.("x + y")); // Throws because x is not defined in global scope
    }
    
  • Косвенный eval не наследует строгость окружающего контекста и находится в строгом режиме только в том случае, если сама строка источника имеет директиву "use strict".

    function nonStrictContext() {
      eval?.(`with (Math) console.log(PI);`);
    }
    function strictContext() {
      "use strict";
      eval?.(`with (Math) console.log(PI);`);
    }
    function strictContextStrictEval() {
      "use strict";
      eval?.(`"use strict"; with (Math) console.log(PI);`);
    }
    nonStrictContext(); // Logs 3.141592653589793
    strictContext(); // Logs 3.141592653589793
    strictContextStrictEval(); // Uncaught SyntaxError: Strict mode code may not include a with statement
    

    С другой стороны, прямой eval наследует строгость вызывающего контекста.

    function nonStrictContext() {
      eval(`with (Math) console.log(PI);`);
    }
    function strictContext() {
      "use strict";
      eval(`with (Math) console.log(PI);`);
    }
    function strictContextStrictEval() {
      "use strict";
      eval(`"use strict"; with (Math) console.log(PI);`);
    }
    nonStrictContext(); // Logs 3.141592653589793
    strictContext(); // Uncaught SyntaxError: Strict mode code may not include a with statement
    strictContextStrictEval(); // Uncaught SyntaxError: Strict mode code may not include a with statement
    
  • Переменные, объявленные с помощью var, и объявления функций попадут в окружающую область видимости, если строка источника не интерпретируется в строгом режиме — для косвенного eval они станут глобальными переменными. Если это прямой eval в контексте строгого режима или если исходная строка eval сама находится в строгом режиме, то var и объявления функций не «утекают» в окружающую область видимости.

    // Neither context nor source string is strict,
    // so var creates a variable in the surrounding scope
    eval("var a = 1;");
    console.log(a); // 1
    // Context is not strict, but eval source is strict,
    // so b is scoped to the evaluated script
    eval("'use strict'; var b = 1;");
    console.log(b); // ReferenceError: b is not defined
    
    function strictContext() {
      "use strict";
      // Context is strict, but this is indirect and the source
      // string is not strict, so c is still global
      eval?.("var c = 1;");
      // Direct eval in a strict context, so d is scoped
      eval("var d = 1;");
    }
    strictContext();
    console.log(c); // 1
    console.log(d); // ReferenceError: d is not defined
    

    let и const объявления внутри вычисляемой строки всегда ограничены областью видимости этого скрипта.

  • Прямой eval может иметь доступ к дополнительным контекстным выражениям. Например, в теле функции можно использовать new.target:

    function Ctor() {
      eval("console.log(new.target)");
    }
    new Ctor(); // [Function: Ctor]
    

Никогда не используйте прямой eval()!

Использование прямого eval() страдает от множества проблем:

  • eval() выполняет код, который ему передается, с привилегиями вызывающего. Если вы запускаете eval() со строкой, на которую может повлиять злоумышленник, вы можете выполнить вредоносный код на машине пользователя с разрешениями вашей веб-страницы / расширения. Что еще более важно, разрешение стороннему коду доступа к области видимости, в которой был вызван eval() (если это прямой eval), может привести к возможным атакам, которые считывают или изменяют локальные переменные. См. Соображения безопасности для подходов, смягчающих эти риски.
  • eval() медленнее альтернатив, так как ему приходится вызывать интерпретатор JavaScript, в то время как многие другие конструкции оптимизируются современными движками JS.
  • Современные интерпретаторы JavaScript преобразуют JavaScript в машинный код. Это означает, что любое понятие именования переменных уничтожается. Таким образом, любое использование eval() заставит браузер выполнять длительный и дорогостоящий поиск имен переменных, чтобы выяснить, где переменная существует в машинном коде, и установить ее значение. Кроме того, новые вещи могут быть введены в эту переменную через eval(), такие как изменение типа этой переменной, заставляющее браузер переоценивать весь сгенерированный машинный код, чтобы компенсировать это.
  • Минификаторы отказываются от любой минификации, если область видимости транзитивно зависит от eval(), потому что иначе eval() не сможет прочитать правильную переменную во время выполнения.

Существует множество случаев, когда использование eval() или связанных с ним методов может быть оптимизировано или вовсе избегнуто.

Использование косвенного eval()

Рассмотрим этот код:

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

Простое использование косвенного eval и принуждение к строгому режиму может значительно улучшить код:

function looseJsonParse(obj) {
  return eval?.(`"use strict";(${obj})`);
}
console.log(looseJsonParse("{ a: 4 - 1, b: function () {}, c: new Map() }"));

Два приведенных выше фрагмента кода могут выглядеть так, как будто они работают одинаково, но это не так; первый, использующий прямой eval, страдает от множества проблем.

  • Он значительно медленнее из-за большего количества проверок области видимости. Обратите внимание на c: new Map() в вычисляемой строке. В версии с косвенным eval объект вычисляется в глобальной области видимости, поэтому интерпретатор может безопасно предположить, что Map относится к глобальному конструктору Map(), а не к локальной переменной с именем Map. Однако в коде, использующем прямой eval, интерпретатор не может этого предположить. Например, в следующем коде Map в вычисляемой строке не относится к window.Map().

    function looseJsonParse(obj) {
      class Map {}
      return eval(`(${obj})`);
    }
    console.log(looseJsonParse(`{ a: 4 - 1, b: function () {}, c: new Map() }`));
    

    Таким образом, в версии кода eval() браузер вынужден выполнять дорогостоящий вызов поиска, чтобы проверить, существуют ли какие-либо локальные переменные с именем Map().

  • Если не использовать строгий режим, объявления var внутри исходного кода eval() становятся переменными в окружающей области видимости. Это приводит к трудно отлаживаемым проблемам, если строка получена из внешнего ввода, особенно если существует переменная с таким же именем.
  • Прямой eval может считывать и изменять привязки в окружающей области видимости, что может привести к повреждению локальных данных внешним вводом.
  • При использовании прямого eval, особенно когда источник eval не может быть доказан как находящийся в строгом режиме, движок — и инструменты сборки — должны отключить все оптимизации, связанные с встраиванием, потому что источник eval() может зависеть от любого имени переменной в его окружающей области видимости.

Однако использование косвенного eval() не позволяет передавать дополнительные привязки, кроме существующих глобальных переменных, для чтения вычисляемым источником. Если вам нужно указать дополнительные переменные, к которым вычисляемый источник должен иметь доступ, рассмотрите использование конструктора Function().

Использование конструктора Function()

Конструктор Function() очень похож на приведенный выше пример косвенного eval: он также вычисляет передаваемый ему JavaScript-код в глобальной области видимости, не читая и не изменяя локальные привязки, и поэтому позволяет движкам выполнять больше оптимизаций, чем прямой eval().

Разница между eval() и Function() заключается в том, что строка источника, передаваемая Function(), парсится как тело функции, а не как скрипт. Есть несколько нюансов — например, вы можете использовать инструкции return на верхнем уровне тела функции, но не в скрипте.

Конструктор Function() полезен, если вы хотите создать локальные привязки внутри вашего eval-источника, передавая переменные в качестве привязок параметров.

function add(a, b) {
  return a + b;
}
function runCodeWithAddFunction(obj) {
  return Function("add", `"use strict";return (${obj});`)(add);
}
console.log(runCodeWithAddFunction("add(5, 7)")); // 12

Как eval(), так и Function() неявно вычисляют произвольный код и запрещены в строгих настройках CSP. Существуют также дополнительные более безопасные (и более быстрые!) альтернативы eval() или Function() для распространенных случаев использования.

Использование квадратных скобок

Вы не должны использовать eval() для динамического доступа к свойствам. Рассмотрим следующий пример, где свойство объекта, к которому нужно получить доступ, неизвестно до выполнения кода. Это можно сделать с помощью eval():

const obj = { a: 20, b: 30 };
const propName = getPropName(); // returns "a" or "b"

const result = eval(`obj.${propName}`);

Однако eval() здесь не требуется — на самом деле, это более подвержено ошибкам, потому что, если propName не является допустимым идентификатором, это приведет к синтаксической ошибке. Более того, если getPropName не является функцией, которую вы контролируете, это может привести к выполнению произвольного кода. Вместо этого используйте аксессоры свойств, которые намного быстрее и безопаснее:

const obj = { a: 20, b: 30 };
const propName = getPropName(); // returns "a" or "b"
const result = obj[propName]; // obj["a"] is the same as obj.a

Вы можете даже использовать этот метод для доступа к потомкам свойств. Используя eval(), это будет выглядеть так:

const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"

const result = eval(`obj.${propPath}`); // 0

Избежать eval() здесь можно, разделив путь к свойству и перебирая различные свойства:

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

const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"
const result = getDescendantProp(obj, propPath); // 0

Установка свойства таким образом работает аналогично:

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

const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"
const result = setDescendantProp(obj, propPath, 1); // obj.a.b.c is now 1

Однако имейте в виду, что использование квадратных скобок с неограниченным вводом также небезопасно — это может привести к атакам внедрения объектов.

Использование обратных вызовов

В JavaScript есть функции первого класса, что означает, что вы можете передавать функции в качестве аргументов другим API, хранить их в переменных и свойствах объектов и так далее. Многие DOM API разработаны с учетом этого, поэтому вы можете (и должны) написать:

// Instead of setTimeout("…", 1000) use:
setTimeout(() => {
  // …
}, 1000);

// Instead of elt.setAttribute("onclick", "…") use:
elt.addEventListener("click", () => {
  // …
});

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

Использование JSON

Если строка, на которой вы вызываете eval(), содержит данные (например, массив: "[1, 2, 3]"), а не код, вам следует рассмотреть возможность перехода на JSON, который позволяет строке использовать подмножество синтаксиса JavaScript для представления данных.

Обратите внимание, что поскольку синтаксис JSON ограничен по сравнению с синтаксисом JavaScript, многие допустимые литералы JavaScript не будут разбираться как JSON. Например, конечные запятые не допускаются в JSON, а имена свойств (ключи) в литералах объектов должны быть заключены в кавычки. Обязательно используйте JSON-сериализатор для создания строк, которые затем будут парситься как JSON.

Передача тщательно ограниченных данных вместо произвольного кода в целом является хорошей идеей. Например, расширение, предназначенное для извлечения содержимого веб-страниц, может иметь правила извлечения, определенные в XPath, а не в коде JavaScript.

Соображения безопасности

Метод может использоваться для выполнения произвольного ввода с привилегиями вызывающего. Если ввод представляет собой потенциально небезопасную строку, предоставленную пользователем, это является возможным вектором для атак межсайтового скриптинга (XSS).

Например, следующий код показывает, как eval() может выполнять untrustedCode, предоставленный пользователем:

const untrustedCode = "alert('Potentially evil code!');";
const adder = eval(untrustedCode);

Веб-сайты с Content Security Policy (CSP), которая определяет script-src или default-src, по умолчанию запретят выполнение такого кода. Если вам нужно разрешить выполнение скриптов через eval(), вы можете снизить риски, всегда назначая экземпляр TrustedScript вместо строки, и принудительно применять доверенные типы с помощью директивы CSP require-trusted-types-for. Это гарантирует, что входные данные проходят через функцию преобразования.

Чтобы разрешить выполнение eval(), вам также необходимо указать ключевое слово trusted-types-eval в вашей директиве CSP script-src. Ключевое слово unsafe-eval также разрешает eval(), но намного менее безопасно, чем trusted-types-eval, поскольку оно разрешило бы выполнение даже в браузерах, которые не поддерживают доверенные типы.

Например, требуемая CSP для вашего сайта может выглядеть так:

Content-Security-Policy: require-trusted-types-for 'script'; script-src '<your_allowlist>' 'trusted-types-eval'

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

Примеры

Обратите внимание, что первый пример показывает, как использовать метод с доверенными типами. Другие примеры опускают этот шаг для краткости.

Использование TrustedScript

Для снижения риска XSS мы всегда должны присваивать экземпляры TrustedScript параметру script. Мы также должны делать это, если мы принудительно применяем доверенные типы по другим причинам и хотим разрешить некоторые источники скриптов, которые были разрешены (через CSP: script-src).

Доверенные типы еще не поддерживаются во всех браузерах, поэтому сначала мы определяем доверенные типы tinyfill. Это действует как прозрачная замена JavaScript API Trusted Types:

if (typeof trustedTypes === "undefined")
  trustedTypes = { createPolicy: (n, rules) => rules };

Далее мы создаем TrustedTypePolicy, который определяет метод createScript() для преобразования входных строк в экземпляры TrustedScript.

Для целей этого примера мы предположим, что у нас есть функция transformedScript(), которая определяет нашу логику преобразования/фильтрации.

const policy = trustedTypes.createPolicy("script-policy", {
  createScript(input) {
    const transformed = transformedScript(input); // Our filter method
    return transformed;
  },
});

Затем мы используем объект policy для создания объекта TrustedScript из потенциально небезопасной входной строки:

// The potentially malicious string
const untrustedScript = "alert('Potentially evil code!');";

// Create a TrustedScriptURL instance using the policy
const trustedScript = policy.createScript(untrustedScript);

Объект TrustedScript теперь может быть передан в eval():

eval(trustedScript);

Использование eval()

В следующем коде оба оператора, содержащие eval(), возвращают 42. Первый вычисляет строку "x + y + 1"; второй вычисляет строку "42".

const x = 2;
const y = 39;
const z = "42";
eval("x + y + 1"); // 42
eval(z); // 42

eval() возвращает завершающее значение операторов

eval() возвращает завершающее значение операторов. Для if это будет последнее вычисленное выражение или оператор.

const str = "if (a) { 1 + 1 } else { 1 + 2 }";
let a = true;
let b = eval(str);

console.log(`b is: ${b}`); // b is: 2

a = false;
b = eval(str);

console.log(`b is: ${b}`); // b is: 3

Следующий пример использует eval() для вычисления строки str. Эта строка состоит из операторов JavaScript, которые присваивают z значение 42, если x равно пяти, и присваивают 0 z в противном случае. Когда выполняется второй оператор, eval() приведет к выполнению этих операторов, а также вычислит набор операторов и вернет значение, которое присваивается z, поскольку завершающее значение присваивания — это присвоенное значение.

const x = 5;
const str = `if (x === 5) {
  console.log("z is 42");
  z = 42;
} else {
  z = 0;
}`;

console.log("z is ", eval(str)); // z is 42  z is 42

Если вы присваиваете несколько значений, возвращается последнее значение.

let x = 5;
const str = `if (x === 5) {
  console.log("z is 42");
  z = 42;
  x = 420;
} else {
  z = 0;
}`;

console.log("x is", eval(str)); // z is 42  x is 420

eval() как строка, определяющая функцию, требует "(" и ")" в качестве префикса и суффикса

// This is a function declaration
const fctStr1 = "function a() {}";
// This is a function expression
const fctStr2 = "(function b() {})";
const fct1 = eval(fctStr1); // return undefined, but `a` is available as a global function now
const fct2 = eval(fctStr2); // return the function `b`

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

Спецификация
ECMAScript® 2027 Language Specification
# sec-eval-x

Совместимость с браузерами

Desktop Mobile Server
Chrome Edge Firefox Opera Safari Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet WebView Android WebView on iOS Bun Deno Node.js
eval
1
12
1
3
1
18
4
10.1
1
1.0
4.4
1
1.0.0
1.0
0.10.0

См. также

  • Доступ к свойствам
  • WebExtensions: Использование eval в скриптах контента

© 2005–2025 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API