JavaScript eval()
Aprende cómo eval() ejecuta una cadena como código: sintaxis, valor de retorno, llamadas directas vs. indirectas, riesgos de seguridad y alternativas más seguras como el constructor Function y JSON.parse().
JavaScript puede convertir una cadena en código ejecutable en tiempo de ejecución. La función integrada eval() hace exactamente eso: recibe una cadena, la trata como código fuente JavaScript y la ejecuta. Esta página explica cómo funciona eval(), cuándo devuelve un valor, por qué casi siempre es la herramienta equivocada y qué usar en su lugar.
Esta página cubre:
- La sintaxis de
eval()y qué devuelve. - Casos de uso reales (y por qué la mayoría tienen mejores alternativas hoy en día).
- Los problemas de seguridad y rendimiento que hacen que
eval()sea peligroso. - Alternativas más seguras: el constructor
FunctionyJSON.parse(). - Buenas prácticas para los casos raros en que realmente lo necesites.
Entendiendo la función eval()
eval() evalúa o ejecuta una cadena de código JavaScript. Si la cadena es una expresión, eval() la evalúa y devuelve el resultado. Si la cadena contiene una o más sentencias, eval() las ejecuta y devuelve el valor de la última sentencia de expresión (o undefined).
Sintaxis
eval(string)Parámetros:
string— una cadena de código fuente JavaScript a evaluar.
Valor de retorno: el resultado de la última expresión evaluada, o undefined si la cadena no contiene expresiones.
Si string no es una cadena (por ejemplo un número), eval() devuelve el argumento sin modificarlo.
Ejemplo de uso
En este ejemplo evaluamos una expresión aritmética simple e imprimimos el resultado:
Casos de uso prácticos de eval()
Si bien la función eval() puede ser muy poderosa, su uso suele desaconsejarse por razones de seguridad y rendimiento. Sin embargo, hay escenarios en los que eval() puede ser útil.
Ejecución dinámica de código
En situaciones donde el código debe generarse y ejecutarse de forma dinámica, eval() puede ser una solución práctica. Por ejemplo, crear una calculadora que evalúe la entrada del usuario como una expresión matemática:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dynamic Calculator</title>
</head>
<body>
<p>Add any arithmetic expression like 12 + 5 and hit 'Calculate' button!</p>
<div>
<input type="text" id="expression" placeholder="Enter expression" /> <!-- User input field -->
<button onclick="evaluateExpression()">Calculate</button> <!-- Button to trigger calculation -->
</div>
<div id="calcResult"></div> <!-- Element to display result -->
<script>
function evaluateExpression() {
const expression = document.getElementById('expression').value; // Get user input
try {
const result = eval(expression); // Evaluate the expression
document.getElementById('calcResult').textContent = `Result: ${result}`; // Display the result
} catch (e) {
document.getElementById('calcResult').textContent = 'Invalid expression'; // Handle invalid input
}
}
</script>
</body>
</html>Análisis de JSON con eval()
Antes de la introducción de JSON.parse(), se usaba eval() para analizar cadenas JSON. Sin embargo, ahora se recomienda usar JSON.parse() por razones de seguridad.
Al usar eval() para analizar cadenas JSON, es fundamental asegurarse de que la cadena JSON se interprete como una expresión de objeto y no como una sentencia de bloque. Para lograrlo, la cadena JSON se envuelve entre paréntesis antes de pasarla a eval(). Esto garantiza que JavaScript trate la cadena como una expresión de objeto, evitando errores de sintaxis que podrían ocurrir si la cadena se interpretara como una sentencia de bloque.
Siempre prefiere JSON.parse() en lugar de eval() para analizar JSON. JSON.parse() solo acepta datos, por lo que nunca puede ejecutar un script inyectado.
eval() directo vs. indirecto y alcance
La forma en que eval() accede a las variables depende de cómo se llame.
Una llamada directa — eval(code) escrito literalmente — ejecuta el código en el ámbito actual. Puede leer e incluso crear variables donde se invoca:
Una llamada indirecta — invocar eval mediante cualquier referencia distinta al nombre literal, como (0, eval)(code) o almacenándolo en una variable — ejecuta el código en el ámbito global. No puede ver las variables locales:
En modo estricto, incluso un eval() directo obtiene su propio ámbito: las variables que declara no se filtran hacia la función que lo contiene. Esta es una razón más por la que el código moderno raramente necesita eval().
Problemas de seguridad y rendimiento
El uso de eval() puede presentar riesgos de seguridad significativos y problemas de rendimiento. A continuación se explica por qué:
Riesgos de seguridad
eval() puede ejecutar cualquier código JavaScript, lo que lo hace vulnerable a ataques de inyección. Un atacante podría inyectar código malicioso, provocando graves brechas de seguridad.
Ejemplo de riesgo de seguridad:
Imagina que tienes una aplicación web que toma la entrada del usuario y la evalúa usando eval(). Sin una validación adecuada, un atacante puede aprovechar esto para ejecutar código malicioso.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>eval() Security Risk Example</title>
</head>
<body>
<div>
<p>Hit 'Run Code' button!</p>
<input type="text" id="userInput" placeholder="Enter code" value="alert('Hacked!')" /> <!-- User input field -->
<button onclick="evaluateUserInput()">Run Code</button> <!-- Button to run code -->
</div>
<div id="userInputResult"></div> <!-- Element to display result -->
<script>
function evaluateUserInput() {
const input = document.getElementById('userInput').value; // Get user input
try {
const result = eval(input); // Evaluate the user input
document.getElementById('userInputResult').textContent = `Result: ${result}`; // Display the result
} catch (e) {
document.getElementById('userInputResult').textContent = 'Error in evaluation'; // Handle evaluation error
}
}
</script>
</body>
</html>En este ejemplo, si un usuario ingresa algo como alert('Hacked!'), la función eval() lo ejecutará, causando una brecha de seguridad al mostrar un cuadro de alerta. Un atacante podría inyectar código más dañino y potencialmente comprometer todo el sistema.
Problemas de rendimiento
eval() ejecuta código en el mismo ámbito desde el que se llama, lo que impide ciertas optimizaciones del motor JavaScript. Esto puede producir un rendimiento más lento comparado con otros enfoques.
Ejemplo de problema de rendimiento:
Consideremos un escenario en el que necesitamos realizar una serie de cálculos múltiples veces. Usar eval() para esta tarea puede afectar significativamente el rendimiento.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Performance Issues with eval()</title>
</head>
<body>
<div id="evalResult"></div>
<div id="functionResult"></div>
<script>
// Using eval()
const evalStartTime = performance.now();
for (let i = 0; i < 100000; i++) {
eval("3 * 4 + 5");
}
const evalEndTime = performance.now();
const evalDuration = evalEndTime - evalStartTime;
document.getElementById('evalResult').textContent = `Time taken with eval(): ${evalDuration} ms`;
// Using Function constructor
const func = new Function('return 3 * 4 + 5');
const funcStartTime = performance.now();
for (let i = 0; i < 100000; i++) {
func();
}
const funcEndTime = performance.now();
const funcDuration = funcEndTime - funcStartTime;
document.getElementById('functionResult').textContent = `Time taken with Function constructor: ${funcDuration} ms`;
</script>
</body>
</html>En este ejemplo, comparamos el rendimiento de eval() con el constructor Function. Ejecutamos una operación aritmética simple (3 * 4 + 5) 100.000 veces usando ambos métodos y medimos el tiempo empleado por cada uno.
- Usando
eval(): El código dentro deeval()se evalúa repetidamente, lo que es más lento porque impide ciertas optimizaciones. - Usando el constructor
Function: El constructorFunctioncrea una función que realiza la misma operación, pero es más rápido porque permite que los motores JavaScript optimicen la ejecución repetida.
Los resultados mostrados en la página web indican el tiempo empleado por cada método, demostrando que eval() es más lento debido a sus problemas de rendimiento.
Evita eval() a menos que sea absolutamente necesario. Recurre primero a alternativas más seguras: el constructor Function para funciones dinámicas, o JSON.parse() para datos.
Alternativas a eval()
Para una ejecución de código más segura y eficiente, considera las siguientes alternativas:
Usar el constructor Function
El constructor Function crea un nuevo objeto función, similar a eval(), pero con mejor seguridad y rendimiento.
Usar JSON.parse()
Para analizar cadenas JSON, JSON.parse() es el método preferido.
Buenas prácticas para usar eval()
Si debes usar eval(), sigue estas buenas prácticas para mitigar los riesgos:
1. Validar la entrada
Asegúrate de que la entrada a eval() esté estrictamente validada para evitar la inyección de código. Esto significa permitir solo caracteres y expresiones seguros.
Ejemplo:
En este ejemplo, usamos una expresión regular para validar la entrada del usuario. Solo se permiten caracteres numéricos y operadores aritméticos básicos. Si la entrada coincide con el patrón, se evalúa con eval(). De lo contrario, se rechaza como inválida.
2. Usar Try-Catch
Envuelve eval() en un bloque try-catch para manejar los posibles errores con elegancia. Esto evita que toda la aplicación falle si eval() encuentra un error.
Ejemplo:
Aquí, manejamos los errores de sintaxis en la entrada del usuario usando un bloque try-catch. Si eval() lanza un error, este se captura y se registra un mensaje de error en lugar de que la aplicación falle.
3. Restringir el ámbito
Minimiza el ámbito de las variables accesibles para eval() para limitar el daño potencial de código malicioso. Usa una expresión de función invocada inmediatamente (IIFE) para crear un ámbito local.
Ejemplo:
En este ejemplo, definimos una función restrictedEval dentro de una IIFE para crear un ámbito local. Esta función evalúa la entrada usando eval() pero con variables locales a y b. Las variables globales a y b no son accesibles, lo que demuestra la restricción de ámbito.
Siguiendo estas buenas prácticas, puedes reducir los riesgos asociados con el uso de eval() mientras aprovechas sus capacidades de ejecución dinámica de código cuando sea necesario.
Conclusión
La función eval() en JavaScript proporciona una herramienta poderosa para la ejecución dinámica de código, pero conlleva riesgos significativos y desventajas de rendimiento. En la práctica, casi cualquier tarea que parezca necesitar eval() tiene una alternativa más segura: usa JSON.parse() para datos, el constructor Function para funciones dinámicas, y accesos a objetos y arrays en lugar de construir nombres de variables a partir de cadenas. Si debes usar eval(), valida la entrada, envuélvela en try...catch y mantén su ámbito lo más reducido posible.
Temas relacionados
- La sintaxis "new Function" — la forma más segura de construir una función a partir de una cadena.
- Trabajando con JSON —
JSON.parse()yJSON.stringify(). - Manejo de errores: try...catch — capturar errores lanzados por código evaluado.
- Modo estricto: "use strict" — cómo el modo estricto cambia el ámbito de
eval().