eval()
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
Warnung:
JavaScript aus einem String auszuführen, stellt ein enormes Sicherheitsrisiko dar. Es ist viel zu einfach für einen Angreifer, beliebigen Code auszuführen, wenn Sie eval()
verwenden. Siehe Niemals direkte eval() verwenden!, unten.
Die eval()
-Funktion wertet JavaScript-Code, der als String dargestellt wird, aus und gibt dessen Abschlusswert zurück. Der Quellcode wird als Skript geparst.
Probieren Sie es aus
Syntax
eval(script)
Parameter
script
-
Ein String, der einen JavaScript-Ausdruck, eine Anweisung oder eine Sequenz von Anweisungen darstellt. Der Ausdruck kann Variablen und Eigenschaften vorhandener Objekte enthalten. Es wird als Skript geparst, daher sind
import
-Deklarationen (die nur in Modulen existieren können) nicht erlaubt.
Rückgabewert
Der Abschlusswert der Auswertung des gegebenen Codes. Wenn der Abschlusswert leer ist, wird undefined
zurückgegeben. Wenn script
kein primitiver String ist, gibt eval()
das Argument unverändert zurück.
Ausnahmen
Wirft jede Ausnahme, die während der Auswertung des Codes auftritt, einschließlich SyntaxError
, wenn script
nicht als Skript geparst werden kann.
Beschreibung
eval()
ist eine Funktions-Eigenschaft des globalen Objekts.
Das Argument der eval()
-Funktion ist ein String. Er wird die Quellzeichenfolge als Skripttext auswerten, was bedeutet, dass sowohl Anweisungen als auch Ausdrücke erlaubt sind. Der Abschlusswert des Codes wird zurückgegeben. Für Ausdrücke ist es der Wert, zu dem der Ausdruck ausgewertet wird. Viele Anweisungen und Deklarationen haben ebenfalls Abschlusswerte, aber das Ergebnis kann überraschend sein (z.B. der Abschlusswert einer Zuweisung ist der zugewiesene Wert, aber der Abschlusswert von let
ist undefined), daher wird empfohlen, sich nicht auf Abschlusswerte von Anweisungen zu verlassen.
Im strikten Modus führt die Deklaration einer Variablen namens eval
oder die Neu-Zuweisung von eval
zu einem SyntaxError
.
"use strict";
const eval = 1; // SyntaxError: Unexpected eval or arguments in strict mode
Wenn das Argument von eval()
kein String ist, gibt eval()
das Argument unverändert zurück. Im folgenden Beispiel führt das Übergeben eines String
-Objekts anstelle eines primitiven Strings dazu, dass eval()
das String
-Objekt zurückgibt, anstatt den String auszuwerten.
eval(new String("2 + 2")); // returns a String object containing "2 + 2"
eval("2 + 2"); // returns 4
Um das Problem auf eine generische Weise zu umgehen, können Sie das Argument selbst in einen String umwandeln, bevor Sie es an eval()
übergeben.
const expression = new String("2 + 2");
eval(String(expression)); // returns 4
Direkte und indirekte eval
Es gibt zwei Modi für eval()
-Aufrufe: direktes eval und indirektes eval. Direktes eval bedeutet, die globale eval
-Funktion direkt mit eval(...)
aufzurufen. Alles andere, einschließlich der Verwendung über eine zugeordnete Variable, den Zugriff über ein Mitglied oder andere Ausdrücke oder durch den optionalen Verkettungsoperator ?.
, ist indirekt.
// 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");
Indirektes eval kann so gesehen werden, als ob der Code innerhalb eines separaten <script>
-Tags ausgewertet wird. Das bedeutet:
-
Indirektes eval funktioniert im globalen Gültigkeitsbereich statt im lokalen Gültigkeitsbereich, und der ausgewertete Code hat keinen Zugriff auf lokale Variablen innerhalb des Aufrufs.
jsfunction 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 }
-
Indirektes
eval
übernimmt nicht den strikten Modus des umgebenden Kontexts und ist nur im strikten Modus, wenn die Quellzeichenfolge selbst eine"use strict"
-Direktive enthält.jsfunction 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
Auf der anderen Seite übernimmt direktes eval die Striktheit des aufrufenden Kontexts.
jsfunction 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
-
Mit
var
deklarierte Variablen und Funktionsdeklarationen würden in den umgebenden Gültigkeitsbereich gehen, wenn die Quellzeichenfolge nicht im strikten Modus interpretiert wird - für indirekte eval werden sie zu globalen Variablen. Wenn es sich um ein direktes eval in einem strikten Modus handelt oder wenn dieeval
-Quellzeichenfolge selbst im strikten Modus ist, "leaken"var
- und Funktionsdeklarationen nicht in den umgebenden Gültigkeitsbereich.js// 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
undconst
-Deklarationen innerhalb der ausgewerteten Zeichenfolge sind immer auf dieses Skript beschränkt. -
Direktes eval hat möglicherweise Zugriff auf zusätzliche kontextbezogene Ausdrücke. Zum Beispiel kann man im Körper einer Funktion
new.target
verwenden:jsfunction Ctor() { eval("console.log(new.target)"); } new Ctor(); // [Function: Ctor]
Niemals direkte eval() verwenden!
Die Verwendung von direktem eval()
hat mehrere Probleme:
eval()
führt den übergebenen Code mit den Berechtigungen des Aufrufers aus. Wenn Sieeval()
mit einem String ausführen, der von einer böswilligen Partei beeinflusst werden könnte, besteht das Risiko, dass bösartiger Code auf dem Rechner des Benutzers mit den Berechtigungen Ihrer Webseite/Erweiterung ausgeführt wird. Wichtiger ist, dass Dritten die Möglichkeit gegeben wird, auf den Gültigkeitsbereich zuzugreifen, in demeval()
aufgerufen wurde (bei direktem eval), was zu möglichen Angriffen führen kann, die lokale Variablen lesen oder ändern.eval()
ist langsamer als Alternativen, da es den JavaScript-Interpreter aufrufen muss, während viele andere Konstrukte von modernen JS-Engines optimiert werden.- Moderne JavaScript-Interpreter wandeln JavaScript in Maschinencode um. Das bedeutet, dass jedes Konzept der Variablennamen eliminiert wird. Daher zwingt jede Verwendung von
eval()
den Browser dazu, lange und teure Variablennamenssuche durchzuführen, um herauszufinden, wo die Variable im Maschinencode existiert und ihren Wert festzulegen. Zusätzlich können neue Dinge dieser Variable durcheval()
hinzugefügt werden, wie das Ändern des Typs dieser Variable, was den Browser zwingt, den gesamten generierten Maschinencode neu zu evaluieren, um dies zu kompensieren. - Minifier geben jede Minifizierung auf, wenn der Gültigkeitsbereich transitiv von
eval()
abhängt, da ansonsteneval()
zur Laufzeit nicht auf die korrekte Variable zugreifen kann.
Es gibt viele Fälle, in denen die Verwendung von eval()
oder verwandter Methoden optimiert oder ganz vermieden werden kann.
Verwenden von indirektem eval()
Sehen Sie sich diesen Code an:
function looseJsonParse(obj) {
return eval(`(${obj})`);
}
console.log(looseJsonParse("{ a: 4 - 1, b: function () {}, c: new Map() }"));
Einfaches Verwenden von indirektem eval und Erzwingen des strikten Modus kann den Code erheblich verbessern:
function looseJsonParse(obj) {
return eval?.(`"use strict";(${obj})`);
}
console.log(looseJsonParse("{ a: 4 - 1, b: function () {}, c: new Map() }"));
Die beiden obigen Code-Snippets scheinen auf die gleiche Weise zu funktionieren, tun es jedoch nicht; die erste Version, die direktes eval verwendet, leidet unter mehreren Problemen.
-
Es ist erheblich langsamer, aufgrund von mehr Gültigkeitsbereich-Überprüfungen. Beachten Sie
c: new Map()
in der ausgewerteten Zeichenfolge. In der indirekten eval-Version wird das Objekt im globalen Gültigkeitsbereich ausgewertet, sodass es für den Interpreter sicher ist anzunehmen, dass sichMap
auf den globalenMap()
-Konstruktor bezieht, anstatt auf eine lokale Variable namensMap
. In dem Code, der direktes eval verwendet, kann der Interpreter dies jedoch nicht annehmen. Beispielweise bezieht sichMap
im ausgewerteten String im folgenden Code nicht aufwindow.Map()
.jsfunction looseJsonParse(obj) { class Map {} return eval(`(${obj})`); } console.log(looseJsonParse(`{ a: 4 - 1, b: function () {}, c: new Map() }`));
Daher wird der Browser in der
eval()
-Version des Codes gezwungen, den teuren Nachschlageaufruf durchzuführen, um zu überprüfen, ob lokale Variablen namensMap()
existieren. -
Wird der strikte Modus nicht verwendet, werden
var
-Deklarationen innerhalb dereval()
-Quelle zu Variablen im umgebenden Gültigkeitsbereich. Dies führt zu schwer zu debuggenden Problemen, wenn die Zeichenfolge aus externen Eingaben stammt, insbesondere wenn es eine vorhandene Variable mit demselben Namen gibt. -
Direktes eval kann Bindungen im umgebenden Gültigkeitsbereich lesen und ändern, was dazu führen kann, dass externe Eingaben lokale Daten beschädigen.
-
Bei Verwendung von direktem
eval
, insbesondere wenn die eval-Quelle nicht im strikten Modus bewiesen werden kann, müssen die Engine und Build-Tools alle Optimierungen im Zusammenhang mit Inlining deaktivieren, da dieeval()
-Quelle von jedem Variablennamen im umgebenden Gültigkeitsbereich abhängen kann.
Jedoch erlaubt indirektes eval()
nicht das Übergeben zusätzlicher Bindungen außer den vorhandenen globalen Variablen, die die ausgewertete Quelle lesen kann. Wenn Sie zusätzliche Variablen angeben müssen, auf die die ausgewertete Quelle zugreifen soll, sollten Sie den Function()
-Konstruktor verwenden.
Verwenden des Function()-Konstruktors
Der Function()
-Konstruktor ist sehr ähnlich zum oben genannten indirekten eval-Beispiel: Er wertet die an ihn übergebene JavaScript-Quelle im globalen Gültigkeitsbereich aus, ohne lokale Bindungen zu lesen oder zu ändern, und ermöglicht es daher den Engines, mehr Optimierungen durchzuführen als direktes eval()
.
Der Unterschied zwischen eval()
und Function()
besteht darin, dass die an Function()
übergebene Quellzeichenfolge als Funktionskörper geparst wird, nicht als Skript. Es gibt einige Nuancen — zum Beispiel können Sie return
-Anweisungen auf der obersten Ebene eines Funktionskörpers verwenden, nicht jedoch in einem Skript.
Der Function()
-Konstruktor ist nützlich, wenn Sie innerhalb Ihrer eval-Quelle lokale Bindungen erstellen möchten, indem Sie die Variablen als Parameterbindungen übergeben.
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
Sowohl eval()
als auch Function()
werten implizit beliebigen Code aus und sind in strengen CSP-Einstellungen verboten. Es gibt auch zusätzliche sicherere (und schnellere!) Alternativen zu eval()
oder Function()
für häufige Anwendungsfälle.
Verwenden von Klammerzugriffen
Sie sollten eval()
nicht verwenden, um Eigenschaften dynamisch zuzugreifen. Betrachten Sie das folgende Beispiel, bei dem die zuzugreifende Eigenschaft des Objekts erst bekannt ist, wenn der Code ausgeführt wird. Dies kann mit eval()
durchgeführt werden:
const obj = { a: 20, b: 30 };
const propName = getPropName(); // returns "a" or "b"
const result = eval(`obj.${propName}`);
Jedoch ist eval()
hier nicht notwendig — in der Tat ist es fehleranfälliger, weil wenn propName
kein gültiger Bezeichner ist, es zu einem Syntaxfehler führt. Außerdem kann, wenn getPropName
keine Funktion ist, die Sie kontrollieren, dies zur Ausführung von beliebigem Code führen. Stattdessen sollten Sie die viel schnelleren und sichereren Eigenschaftszugriffe verwenden:
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
Sie können diese Methode sogar verwenden, um Nachkommeneigenschaften zuzugreifen. Mit eval()
würde dies so aussehen:
const obj = { a: { b: { c: 0 } } };
const propPath = getPropPath(); // suppose it returns "a.b.c"
const result = eval(`obj.${propPath}`); // 0
Um eval()
hier zu vermeiden, könnte man den Eigenschaftenpfad aufteilen und durch die verschiedenen Eigenschaften durchlaufen:
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
Das Festlegen einer Eigenschaft funktioniert ähnlich:
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
Jedoch beachten Sie, dass die Verwendung von Klammerzugriffen mit uneingeschränktem Input ebenfalls nicht sicher ist — es könnte zu Objekt-Injection-Angriffen führen.
Verwenden von Callbacks
JavaScript hat first-class functions, was bedeutet, dass Sie Funktionen als Argumente an andere APIs übergeben, in Variablen und Objekteigenschaften speichern und so weiter können. Viele DOM-APIs sind mit diesem Gedanken im Sinn entworfen worden, sodass Sie (und sollten) schreiben:
// Instead of setTimeout("…", 1000) use:
setTimeout(() => {
// …
}, 1000);
// Instead of elt.setAttribute("onclick", "…") use:
elt.addEventListener("click", () => {
// …
});
Verschlüsse sind ebenfalls hilfreich als eine Methode, um parametrisierte Funktionen zu erstellen, ohne Zeichenfolgen zusammenzuführen.
Verwenden von JSON
Wenn der String, den Sie mit eval()
aufrufen, Daten (zum Beispiel ein Array: "[1, 2, 3]"
) anstelle von Code enthält, sollten Sie in Betracht ziehen, auf JSON umzusteigen, das es der Zeichenfolge erlaubt, ein Teilset von JavaScript-Syntax zu verwenden, um Daten darzustellen.
Beachten Sie, dass JSON-Syntax im Vergleich zur JavaScript-Syntax eingeschränkt ist, sodass viele gültige JavaScript-Literale nicht als JSON geparst werden. Beispielweise sind nachgestellte Kommata in JSON nicht erlaubt und Eigenschaftsnamen (Schlüssel) in Objektliteralen müssen in Anführungszeichen stehen. Verwenden Sie unbedingt einen JSON-Serializer, um Zeichenfolgen zu generieren, die später als JSON geparst werden.
Das Übergeben vorsichtig eingeschränkter Daten statt beliebigen Codes ist generell eine gute Idee. Beispielsweise könnte eine Erweiterung, die darauf ausgelegt ist, Inhalte von Webseiten zu scrapen, die Scraping-Regeln in XPath anstelle von JavaScript-Code definieren.
Beispiele
Verwenden von eval()
Im folgenden Code geben beide Anweisungen mit eval()
den Wert 42 zurück.
Die erste wertet den String "x + y + 1"
aus; die zweite wertet den String
"42"
aus.
const x = 2;
const y = 39;
const z = "42";
eval("x + y + 1"); // 42
eval(z); // 42
eval() gibt den Abschlusswert von Anweisungen zurück
eval()
gibt den Abschlusswert von Anweisungen zurück. Für if
wäre es der letzte ausgewertete Ausdruck oder die letzte Anweisung.
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
Das folgende Beispiel verwendet eval()
, um die Zeichenfolge str
auszuwerten. Diese Zeichenfolge besteht aus JavaScript-Anweisungen, die z
einen Wert von 42 zuweisen, wenn x
fünf ist, und z
andernfalls 0 zuweisen. Wenn die zweite Anweisung ausgeführt wird, wird eval()
bewirken, dass diese Anweisungen ausgeführt werden, und es wird auch die Anweisungseingabe ausführen und den Wert zurückgeben, der z
zugewiesen wird, weil der Abschlusswert einer Zuweisung der zugewiesene Wert ist.
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
Wenn Sie mehrere Werte zuweisen, wird der letzte Wert zurückgegeben.
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() als eine Zeichenfolge, die eine Funktion definiert, erfordert "(" und ")" als Präfix und Suffix
// 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`
Spezifikationen
Specification |
---|
ECMAScript Language Specification # sec-eval-x |
Browser-Kompatibilität
BCD tables only load in the browser