Creating a JavaScript Calculator Without `eval()`

Creating a JavaScript Calculator Without `eval()`

D
dongAuthor
6 min read

When writing JavaScript code, there are times when you need to execute code dynamically. In such cases, many developers often think of the eval() function. eval() provides a convenient feature that interprets a string as code and executes it. However, behind this convenience lie serious security risks and performance degradation.

In this article, we’ll explore why using eval() is dangerous and take a detailed look at how to use the safer and more efficient alternative, new Function(), to execute dynamic code. Through this post, you’ll gain the ability to write safer and more robust code.

What is eval() and Why is It Dangerous?

eval() is a global function that interprets and executes a string as JavaScript code. For example, eval("2 + 2") returns the number 4. This ability to execute code expressed as a string dynamically may seem useful for implementing logic such as a calculator.

However, using eval() can lead to several serious issues.

Learn more about eval() in detail!

1. Security Vulnerabilities

The biggest problem with eval() is security. This function executes code with the permissions of the scope in which it’s called, so if user input is executed via eval() without validation, malicious code can be executed as-is.

For example, imagine a piece of code that handles user input via eval():

var userContent = getUserInput(); // Function that receives user input
eval(userContent); // Dangerous! The user input can be executed as code.

If the user enters a string like "alert('You have been hacked!')", the script will be executed immediately, causing unexpected behavior. This can lead to serious threats like leaking sensitive data or causing service outages.

2. Performance Degradation

eval() hinders the optimization of JavaScript engines. Modern JavaScript engines (like JIT compilers) analyze and optimize code before execution. But when eval() is used, the engine cannot predict which variables the internal code may reference or modify.

As a result, the engine has to handle operations like variable name resolution in a slower manner and may even need to re-interpret compiled code. This leads to overall performance degradation of the application.

A Safer Alternative: new Function()

There is a better way to execute code dynamically without the risks of eval(): using the new Function() constructor.

new Function() takes a list of parameters and a function body in string form, and returns a new function object.

How to Use new Function()

The basic syntax is:

let func = new Function ([arg1, arg2, ...argN], functionBody);

For example, here’s how to create a function that adds two numbers using new Function():

const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3));  // Output: 5

As you can see, new Function() allows you to dynamically create a function with logic defined in a string.

Why is new Function() Safer?

The most important difference between new Function() and eval() lies in the execution context.

1. Difference in Execution Context

  • eval(): Executes code in the local scope of the current context. It can access and modify local variables of the function where it’s called.

  • new Function(): Always creates a function that runs in the global scope. Functions created with new Function() cannot access the local scope in which they were created.

2. Enhanced Security Through Scope Limitation

Functions created with new Function() do not form closures and do not reference external lexical environments. They can only reference the global scope.

Let’s look at an example that clearly shows the difference:

function demo() {
  let localVariable = 'I am a local variable.';

  // eval() can access the local scope
  eval("console.log(localVariable);"); // Output: "I am a local variable."

  // new Function() cannot access the local scope
  try {
    const myFunction = new Function("console.log(localVariable);");
    myFunction();
  } catch (e) {
    console.error(e); // Output: ReferenceError: localVariable is not defined
  }
}

demo();

As shown above, eval() can access the local variable localVariable in the demo function, but the function created by new Function() cannot, resulting in a ReferenceError. This isolation helps prevent malicious code from accessing sensitive local data.

3. Performance Advantages

Unlike eval(), which blocks JavaScript engine optimizations, new Function() can be relatively better for performance. Since the code exists in a separate function body and doesn’t pollute the local scope, the engine can analyze and optimize it more easily.

Of course, new Function() still requires parsing and compiling the string at runtime, making it slower than statically declared functions. However, when dynamic execution is necessary, it is a far better choice than eval().

Best Practices When Using new Function()

Although new Function() is safer than eval(), it can still be risky to directly use user input in the function body. Follow these guidelines when generating dynamic code:

  1. Do not use raw user input directly: For calculator logic, don’t pass the entire user-entered expression directly into new Function(). Instead, parse the input and build the function body only from validated operators (+, –, *, /) and numbers.

  2. Use Strict Mode: Add 'use strict'; at the beginning of the function body (e.g., new Function('"use strict"; ...')) to write safer code. Strict mode treats certain unsafe syntax as errors.

  3. Always consider alternatives first: Reevaluate whether dynamic code generation is really necessary. In many cases, the same functionality can be implemented more safely and efficiently using static functions or data structures (e.g., objects or maps).

Toward Better Code

eval() is powerful and convenient, but it comes with serious security and performance risks. The MDN Web Docs strongly warn against using eval() by saying, “Never use eval!”

Fortunately, we have a safer and more efficient alternative in new Function(). It confines the execution scope to global, eliminating key security vulnerabilities of eval(), and is more favorable for optimization.

Of course, the best practice is to avoid dynamic code generation altogether. But when it’s absolutely necessary, start using new Function() instead of eval() to write safer and more robust code. Your code will improve by a level. :)