Function constructor vs function statement

2020-05-27 04:07发布

问题:

today I've read we have a way of declaring the function by Function constructor. But I never seen the actual implementation that uses Function constructor in real. So I would like to ask, are there any circumstances that we can get benefit by using Function constructor as opposed to using function() declaration? And what are the hidden differences of between?(if there is any)

Function Constructor

var func = new Function("x", "y", "return x*y;"); // pass the context by String

function():

var func = function(x, y){ return x*y; }

Thanks

回答1:

The Function constructor is a form of eval, which should generally be avoided (it's slow and considered insecure). There is really no benefit to using the Function constructor over the built in function statement unless you want to construct a function from dynamic components, which is pretty rare. There are legitimate uses for this form, however most of the time it's used unnecessarily which is why it's looked down upon and generally avoided.

Also, functions created with the function constructor will not keep a reference to the environment they were defined in (the closure). When executed, they will pull those variables directly from the global scope.

var f, a;
(function () {
   var a = 123;
   f = new Function("return a");
})();

f(); //undefined

a = "global"
f(); // "global"

Whereas regular functions do keep a reference to the environment in which they were defined:

var f;
(function () {
   var a = 123;
   f = function () { return a; }
})();
f(); //123


回答2:

Well, the obvious difference when working with strings is that you have the option of meta-programming, by constructing the string at runtime (the same as you could with eval). However, this is double edged, and arguably leads into a range of other problems with literal concatenation (injection), and perhaps simply complexity. If you don't need the string version, I wouldn't use it to be honest.

A side benefit of the regular (non-string) version is that most javascript minifiers (or obfuscators) will know what to do with it. This seems unlikely for the string, i.e. they'll leave it "as is" (not minified or obfuscated).



回答3:

If you're writing a Javascript parser and are interpreting a string as a function then you could use a function constructor. EG if you are given:

"function(x){return x+2}"

And you have some sort of lexical parser and it finds that substring is indeed a function, to translate it into a real function you would use new Function and add it to your tree.

Otherwise there really isn't much of a use that I can think of.



回答4:

Additional notes to Cristian Sanchez' posting.

You can never access local scope vars within a 'Function'-Evaluation.

Lets see the difference between Function and eval.

Function-Example:

var f, a = 1;
(function () {
    var a = 123;
    f = new Function("return a");
})();
console.log(f()) // 1

The Function-Constructor doesn't know anything about the local scope, because it creates a new isolated Scope underneath the window/global-Object - not at the position where it is - that is the main-difference to eval.

Eval-Example:

var f, a = 1;
(function () {
    var a = 123;
    eval("f = function() { return a }");
})();
console.log(f()) // 123

'eval' has access to the local vars. Even if you scope the whole thing.

var f, a = 1;
(function () {
    var a = 123;
    eval("f = (function() { function f() { return a }; return f; })();");
})();
console.log(f()) // still 123

In the following example f() throws an error - "a" is undefined in his own (and global) scope. The Function-Constructor has its own special scope without knowing anything outside - except his parent scope - the window/global-object.

delete a;
var f;
(function () 
{ 
    var a = 1;
    f = new Function("return a");
})();

console.log(f());  // Throws error (a is not defined)

If you want to use local vars, pass it as parameters to the Function-Constructor inside the inner scope!

var f, a = 1;
(function () { 
    var a = 123; 
    f = new Function("a", "return a");

    console.log(f(a)); // passing inner a: result = 123
})();

console.log(f(a)); // passing outer a: result = 1

or

var result, a = 1;
(function () { 
    var a = 123; 
    var f = new Function("a", "return a");
    result = f(a); // store result in global var
})();

console.log(result); // 123

You can execute Function directly with call/apply, too (no "new" needed).

Function('a','b','c', 'console.log(c,b,a)').call(this, 1, 2, 3); // output: 3 2 1
Function('console.log(arguments)').apply(this, [1, 2, 3]); // access through arguments[0-3]

By the way, it's always recommend, setting the strict mode, to enable correct ES5 behaviour.

With strict-mode 'this' is (correctly) undefined by default - until you bind an object.

Function('console.log(this)')(); // this is 'window' in sloppy mode
Function('"use strict"; console.log(this)')(); // this is undefined - correct behavior

If you want, you can bind 'this' explicitly

// bind 'this' to 'window' again
Function('"use strict";console.log(this)').bind(window)();
// bind 'this' to an Object without __proto__
Function('"use strict";console.log(this)').bind(Object.create(null))(); 

Of course you can bind any function/class-object to extend objects.

function SomeClass() 
{
    this.a = 1;
}

var cls = new SomeClass();

new Function('"use strict"; this.a = 5; this.b = 6;').bind(cls)();

console.log(cls.a); // a = 5 - override property
console.log(cls.b); // b = 6 - appended property

Same result with direct call, without 'new' keyword.

function SomeClass() 
{
    this.a = 1;
}

var cls = new SomeClass();

Function('"use strict"; this.a = 5; this.b = 6; this.foo = function(){console.log("bar")}').call(cls);

console.log(cls.a); // a = 5 - override property
console.log(cls.b); // b = 6 - appended property
console.log(cls.foo()); // bar - appended function

Every function/class is created through the the 'Function'-constructor.

So you don't have to write "Function" in your code. You can use the constructor-object, too.

function SomeClass() 
{
    this.a = 1;
}

var cls = new SomeClass();

SomeClass.constructor("this.a = 2;").call(cls);

cls; // SomeClass {a: 2}, because "SomeClass.constructor" === Function

The use of 'Function' is good for example for games, if you have a XMLHttpRequest-Preloader, which loads a very large Javascript-File (5-10 MB bundled scripts), while you want display a loading-bar or something else to the user, instead loading the whole thing with a script-tag, while the user is waiting for page-loading without any visual response.

It does not matter if you load a large script via a script tag, or via Function once on startup. The engine has to load the plain code and evaluate it either way. There are no security aspects, if your(!) code came from a trusted source (your domain) and you know whats inside.

'Function' and 'eval' are only bad with untrusted code (when other users have influence on it) or in loops (slow performance due compiling), but it is totally okay to load & evaluate your own scripts on startup, if the code is the same as in external Javascript-files.