Home  >  Article  >  Web Front-end  >  In-depth understanding of JavaScript series (2) Revealing the secret of named function expressions_javascript skills

In-depth understanding of JavaScript series (2) Revealing the secret of named function expressions_javascript skills

WBOY
WBOYOriginal
2016-05-16 17:57:11939browse

Preface
I haven’t found anyone on the Internet who has repeated in-depth discussions on named function expressions. Because of this, various misunderstandings have appeared on the Internet. This article will discuss JavaScript’s naming functions from both the principle and practice aspects. Advantages and Disadvantages of Expressions.
To put it simply, there is only one user of named function expressions, which is to describe the name of the function during Debug or Profiler analysis. You can also use function names to implement recursion, but you will soon find that it is actually unrealistic. of. Of course, if you don't care about debugging, there's nothing to worry about. Otherwise, if you want to know about compatibility, you should still read on.
Let’s first look at what function expressions are, and then talk about how modern debuggers handle these expressions. If you are already familiar with this, please skip this section.
Function expressions and function declarations
In ECMAScript, the two most common ways to create functions are function expressions and function declarations. The difference between the two is a bit confusing, because the ECMA specification only clarifies one point: The function declaration must have an identifier (Identifier) ​​(which is what everyone often calls the function name), while the function expression can omit this identifier:
Function declaration:
function function name (parameter: optional) { Function body}
Function expression:
Function function name (optional) (Parameter: optional) { Function body}
So, it can be seen that if the function name is not declared, it must be an expression , but if a function name is declared, how to determine whether it is a function declaration or a function expression? ECMAScript differentiates by context. If function foo(){} is part of an assignment expression, it is a function expression. If function foo(){} is contained within a function body, or is located in the program At the top, it's a function declaration.

Copy code The code is as follows:

function foo(){} // declaration, because it is part of the program
var bar = function foo(){}; // expression, because it is part of the assignment expression
new function bar(){}; // expression, because it is new Expression
(function(){
function bar(){} // declaration, because it is part of the function body
})();

There is another Function expressions are less common, they are enclosed in parentheses (function foo(){}). The reason why they are expressions is because parentheses () are a grouping operator, and they can only contain expressions inside. Let’s Look at a few examples:
Copy code The code is as follows:

function foo(){} / / Function declaration
(function foo(){}); // Function expression: included in the grouping operator
try {
(var x = 5); // Grouping operator, only Contains expressions but not statements: var here is the statement
} catch(err) {
// SyntaxError
}

You can think of it, when using eval When JSON is executed, the JSON string is usually enclosed in a parentheses: eval('(' json ')'). The reason for this is because the grouping operator, that is, this pair of parentheses, will force the parser to Parse JSON's curly braces into expressions instead of code blocks.
Copy code The code is as follows:

try {
{ "x": 5 } ; // "{" and "}" are parsed into code blocks
} catch(err) {
// SyntaxError
}
({ "x": 5 }); // Grouping The operator forces "{" and "}" to be parsed as object literals

There is a very subtle difference between expressions and declarations. First, function declarations will be parsed and evaluated before any expression. The value is parsed and evaluated first. Even if your declaration is on the last line of code, it will be parsed/evaluated before the first expression in the same scope. Refer to the following example. The function fn is declared after alert. , but when alert is executed, fn is already defined:
Copy code The code is as follows:

alert(fn());
function fn() {
return 'Hello world!';
}

In addition, there is one more thing that needs to be reminded, function Although declarations can be used within conditional statements, they are not standardized, which means that different environments may have different execution results, so in this case, it is best to use function expressions:
Copy code The code is as follows:

// Don’t do this!
// Because some browsers will return the first function, while some browsers will return the second one
if (true) {
function foo() {
return ' first';
}
}
else {
function foo() {
return 'second';
}
}
foo();
// On the contrary, in this case, we have to use the function expression
var foo;
if (true) {
foo = function() {
return 'first';
};
}
else {
foo = function() {
return 'second';
};
}
foo();

The actual rules for function declarations are as follows:
Function declarations can only appear within a program or function body. Syntactically, they cannot appear inside a Block ({ ... }), for example, within an if, while, or for statement. Because Block can only contain Statement statements, but not source elements such as function declarations. On the other hand, a closer look at the rules reveals that the only possibility for an expression to appear in a Block is if it is part of an expression statement. However, the specification clearly states that an expression statement cannot begin with the keyword function. What this actually means is that function expressions cannot appear in Statement statements or Blocks (because Blocks are composed of Statement statements).

Function Statement
Among the syntax extensions of ECMAScript, one is a function statement. Currently, only browsers based on Gecko implement this extension, so for the following examples, we only use them for learning purposes It seems that it is generally not recommended (unless you are developing for the Gecko browser).
1. Where general statements can be used, function statements can also be used, including in Block blocks:
Copy code The code is as follows:

if (true) {
function f(){ }
}
else {
function f(){ }
}

2. Function statements can be parsed like other statements, including conditional execution
Copy code The code is as follows:

if (true) {
function foo(){ return 1; }
}
else {
function foo(){ return 2 ; }
}
foo(); // 1
// Note: Other clients will parse foo into a function declaration
// Therefore, the second foo will overwrite the first one, The result returns 2 instead of 1

3. Function statements are not declared during variable initialization, but at runtime - the same as function expressions. However, once the identifier of a function statement is declared, it takes effect in the entire scope of the function. Identifier validity is what makes function statements different from function expressions (we will show the specific behavior of named function expressions in the next section).
Copy the code The code is as follows:

// At this moment, foo has no declaration
typeof foo; // "undefined"
if (true) {
// After entering here, foo is declared in the entire scope
function foo(){ return 1; }
}
else {
// Never comes here, so foo here will not be declared
function foo(){ return 2; }
}
typeof foo; // "function"

However, we can use the following standards-compliant code to model the function statement in the above example:
Copy code The code is as follows:

var foo;
if (true) {
foo = function foo(){ return 1; };
}
else {
foo = function foo() { return 2; };
}

4. Characters for function statements and function declarations (or named function expressions) The string representation is similar, including identifiers:
Copy code The code is as follows:

if (true ) {
function foo(){ return 1; }
}
String(foo); // function foo() { return 1; }

5. Another one , there is a bug in the early Gecko-based implementation (Firefox 3 and previous versions), that is, the function statement overrides the function declaration incorrectly. In these early implementations, the function statement somehow could not override the function declaration:
Copy the code The code is as follows:

// Function declaration
function foo(){ return 1; }
if (true) {
// Rewrite
function foo(){ return 2 with function statement ; }
}
foo(); // Returns 1 for FF3 and below, 2 for FF3.5 and above
// However, if it is preceded by a function expression, it is useless
var foo = function(){ return 1; };
if (true) {
function foo(){ return 2; }
}
foo(); // All versions return 2

I emphasize again that the above examples are only supported by certain browsers, so it is recommended that you do not use these unless you are developing on a browser with special features.
Named function expressions
Function expressions are still very common in practical applications. A common pattern in web development is to disguise function definitions based on testing of certain characteristics to achieve performance optimization. , but since this method is within the same scope, you must basically use function expressions:
Copy code Code As follows:

// This code comes from Garrett Smith’s APE Javascript library (http://dhtmlkitchen.com/ape/)
var contains = (function() {
var docEl = document.documentElement;
if (typeof docEl.compareDocumentPosition != 'undefined') {
return function(el, b) {
return (el.compareDocumentPosition(b) & 16) !== 0;
};
}
else if (typeof docEl.contains != 'undefined') {
return function(el, b) {
return el !== b && el .contains(b);
};
}
return function(el, b) {
if (el === b) return false;
while (el != b && (b = b.parentNode) != null);
return el === b;
};
})();

mentioned named function expressions Of course, it must have a name. The previous example var bar = function foo(){}; is a valid named function expression, but there is one thing to remember: this name is only valid within the scope of the newly defined function. Because the specification stipulates that identifiers cannot be valid in the outer scope:
Copy code The code is as follows:

var f = function foo(){
return typeof foo; // foo is valid within the internal scope
};
// foo is invisible when used externally
typeof foo; // "undefined"
f(); // "function"

Since this is the requirement, what is the use of named function expressions? Why a name?
As we said at the beginning: giving it a name can make the debugging process more convenient, because when debugging, if each item in the call stack has its own name to describe it, then the debugging process will be great. Yes, the feeling is different.
Function name in debugger
If a function has a name, the debugger will display its name on the call stack during debugging. Some debuggers (Firebug) sometimes name and display your functions so that they have the same role as those that use the function, but usually, these debuggers only install simple rules for naming, so Speaking of not having a big price, let’s look at an example:
Copy code The code is as follows:

function foo(){
return bar();
}
function bar(){
return baz();
}
function baz(){
debugger;
}
foo();
// Here we use 3 function declarations with names
// So when the debugger goes to the debugger statement, the Firebug call stack looks like Very clear
// Because the name is clearly displayed
baz
bar
foo
expr_test.html()

By viewing the call stack information , we can clearly know that foo calls bar, and bar calls baz (and foo itself is called in the global scope of the expr_test.html document). However, there is another cool place, that is, the Firebug just mentioned is Function for naming anonymous expressions:
Copy code The code is as follows:

function foo() {
return bar();
}
var bar = function(){
return baz();
}
function baz(){
debugger;
}
foo();
// Call stack
baz
bar() //See it?
foo
expr_test.html()

Then, when the function expression is a little more complex, the debugger is not so smart and we can only see it in the call stack Question mark:
Copy code The code is as follows:

function foo(){
return bar();
}
var bar = (function(){
if (window.addEventListener) {
return function( ){
return baz();
};
}
else if (window.attachEvent) {
return function() {
return baz();
} ;
}
})();
function baz(){
debugger;
}
foo();
// Call stack
baz
(?)() // This is a question mark
foo
expr_test.html()

In addition, when assigning a function to multiple variables, the command will also appear Depressing questions:
Copy code The code is as follows:

function foo(){
return baz();
}
var bar = function(){
debugger;
};
var baz = bar;
bar = function() {
alert('spoofed');
};
foo();
// Call stack:
bar()
foo
expr_test.html()

At this time, the call stack shows that foo calls bar, but in fact this is not the case. The reason for this problem is that baz and another function containing alert('spoofed') have made a reference exchange caused.
In the final analysis, the most delegated method is to give the function expression a name, which is to use named function expressions. Let’s rewrite the above example using named expressions (note that the names of the two functions returned in the immediately called expression block are bar):
Copy the code The code is as follows:

function foo(){
return bar();
}
var bar = (function() {
if (window.addEventListener) {
return function bar(){
return baz();
};
}
else if (window.attachEvent) {
return function bar() {
return baz();
};
}
})();
function baz(){
debugger;
}
foo();
// I can see the clear call stack information again!
baz
bar
foo
expr_test.html()

OK, have you learned another trick? But before we get too excited, let's take a look at the unusual JScript.
Bugs in JScript
What’s worse is that IE’s ECMAScript implementation of JScript seriously confuses named function expressions, causing many people to object to named function expressions, and even the latest version (IE8) Version 5.8 used) still has the following problems.
Let’s take a look at what mistakes IE made in its implementation. As the saying goes, only by knowing the enemy can you be invincible. Let’s take a look at the following examples:
Example 1: The identifier of the function expression leaks to the outer scope
Copy code The code is as follows:

var f = function g(){};
typeof g; // "function"

As we said above, naming The identifier of a function expression is invalid in the external scope, but JScript obviously violates this specification. The identifier g in the above example is parsed into a function object, which is messy. Many bugs that are difficult to find are It's because of this reason.
Example 2: Treat named function expressions as function declarations and function expressions at the same time
Copy code The code is as follows:

typeof g; // "function"
var f = function g(){};

In feature environment, function declaration will take precedence over any expression is parsed, the above example shows that JScript actually treats the named function expression as a function declaration, because it parses g before the actual declaration.
This example leads to the next one.
Example 3: Named function expression will create two completely different function objects!
Copy code The code is as follows:

var f = function g(){};
f === g; // false
f.expando = 'foo';
g.expando; // undefined

Seeing this, everyone will think that the problem is serious , because modifying any object will not change the other one, which is too evil. Through this example, we can find that creating two different objects, that is to say, if you want to modify the attribute of f to save certain information, and then use it as a matter of course by referencing the same name attribute of g of the same object, then there will be a big problem. Because it's simply impossible.
Let’s look at a slightly more complicated example:
Example 4: Only parse function declarations sequentially and ignore conditional statement blocks
Copy code The code is as follows:

var f = function g() {
return 1;
};
if (false) {
f = function g(){
return 2;
};
}
g(); // 2

This bug is much more difficult to find, but the cause of the bug is very simple. First, g is parsed as a function declaration. Since function declarations in JScript are not subject to conditional code blocks, in this nasty if branch, g is treated as another function function g(){ return 2 }, also It was just declared again. Then, all "regular" expressions are evaluated, and f is given a reference to another newly created object. Since the abominable if branch "" will never be entered when the expression is evaluated, f will continue to refer to the first function function g(){ return 1 }. After analyzing this, the problem is very clear: if If you are not careful enough and call g in f, then an unrelated g function object will be called.
You may wonder what the difference is when comparing different objects with arguments.callee. ? Let’s take a look:
Copy code The code is as follows:

var f = function g( ){
return [
arguments.callee == f,
arguments.callee == g
];
};
f(); // [true, false]
g(); // [false, true]

As you can see, the reference of arguments.callee is always the called function. In fact, this is also a good thing, which will be explained later.
Another interesting example is using a named function expression in an assignment statement that does not contain a declaration:
Copy code The code is as follows:

(function(){
f = function f(){};
})();

Follow the code According to the analysis, we originally wanted to create a global attribute f (be careful not to confuse it with the general anonymous function, which uses named life). JScript made a mess here. First, it parsed the expression as a function declaration. , so the f on the left is declared as a local variable (the same as the declaration in a general anonymous function), and then when the function is executed, f is already defined, and the function f(){} on the right is directly The value is assigned to the local variable f, so f is not a global attribute at all.

After understanding how abnormal JScript is, we must prevent these problems in time. First, prevent identifier leakage from external scopes. Secondly, we should Never quote identifiers used as function names; remember that annoying identifier g in the previous example? How much unnecessary trouble can be avoided if we can pretend that g does not exist. So, the key is. The key is to always refer to functions via f or arguments.callee. If you use a named function expression, you should only use that name during debugging. Finally, remember to always refer to the named function expression during its declaration. Clean up the erroneously created functions.
Regarding the last point above, we have to explain it again.
JScript’s memory management
After knowing these non-compliant code parsing bugs, how can we use it? , you will find that there is actually a problem with memory. Let’s look at an example:
Copy the code The code is as follows:

var f = (function(){
if (true) {
return function g(){};
}
return function g(){};
})();

We know that this anonymous function calls the returned function (the function with identifier g) and then assigns it to the external f. We also know that named function expressions lead to redundant function objects that are not the same as the returned function objects. So this redundant g function died in the closure of the return function, so memory problems occurred. This is because the function inside the if statement is declared in the same scope as g. In this case, unless we explicitly disconnect the reference to the g function, it will always occupy memory.
Copy code The code is as follows:

var f = (function(){
var f, g;
if (true) {
f = function g(){};
}
else {
f = function g(){};
}
// After setting g to null, it will no longer occupy memory
g = null;
return f;
})();

By setting g to null, the garbage collector reclaims the implicit function referenced by g. In order to verify our code, we will do some tests to ensure that our memory is reclaimed.
Test
The test is very simple, just create 10,000 functions with named function expressions, and then save them in an array. Wait a while and see how much memory these functions take up. Then, disconnect these references and repeat the process. The following is the test code:
Copy code The code is as follows:

function createFn(){
return (function(){
var f;
if (true) {
f = function F(){
return 'standard';
};
}
else if (false) {
f = function F(){
return 'alternative';
};
}
else {
f = function F(){
return 'fallback';
};
}
// var F = null;
return f;
})();
}
var arr = [ ];
for (var i=0; i<10000; i ) {
arr[i] = createFn();
}

By running on Windows XP The following results can be seen in the task manager in SP2:
Copy the code The code is as follows:

IE6:
without `null`: 7.6K -> 20.3K
with `null`: 7.6K -> 18K
IE7:
without `null`: 14K -> 29.7K
with `null`: 14K -> 27K

As we expected, display disconnection can release memory, but the memory released is not a lot, 10,000 function objects only release about 3M This is nothing for some small scripts, but it is very necessary for large programs or when running on low-memory devices for a long time.

There are also some bugs in the parsing of JS in Safari 2.x, but since the version is relatively low, we will not introduce them here. If you want to see them, please check the English information carefully.
Quirks of SpiderMonkey
As we all know, the identifier of a named function expression is only valid in the local scope of the function. But what does the local scope that contains this identifier look like? It's actually very simple. When a named function expression is evaluated, a special object is created whose sole purpose is to hold a property whose name corresponds to the function identifier and whose value corresponds to that function. This object will be injected at the front of the current scope chain. The "extended" scope chain is then used in the initialization function.
One thing that is very interesting here is the way ECMA-262 defines this "special" object (that holds function identifiers). The standard says to create this object "as if you were calling the new Object() expression". If you understand this sentence literally, then this object should be an instance of the global Object. However, there is only one implementation that does this literally, and that implementation is SpiderMonkey. Therefore, in SpiderMonkey, extending Object.prototype may interfere with the local scope of the function:
Copy code The code is as follows:

Object.prototype.x = 'outer';
(function(){
var x = 'inner';
/*
Scope chain of function foo There is a special object in - used to save the identifier of the function. This special object is actually { foo: }. When x is parsed through the scope chain, foo is parsed first. The local environment of Inherited from Object.prototype, so x can be found here.
The value of x is also the value of Object.prototype.x (outer). As a result, the scope of the external function (including the role of x = 'inner'). domain) will not be parsed
*/
(function foo(){
alert(x); // The prompt box displays: outer
})();
})();


However, later versions of SpiderMonkey changed the above behavior, probably because it was considered a security vulnerability. In other words, "special" objects no longer inherit Object.prototype. However, if you use Firefox 3 or lower, you can "revisit" this behavior.
Another browser that implements internal objects as global Object objects is the Blackberry browser. Currently, its activity object (Activation Object) still inherits Object.prototype. However, ECMA-262 does not say that active objects should be created "like calling the new Object() expression" (or like creating objects holding NFE identifiers). Other people's standards only say that the activity object is a mechanism in the standards.
Then let’s take a look at what’s happening in the BlackBerry:
Copy the code The code is as follows:

Object.prototype.x = 'outer';
(function(){
var x = 'inner';
(function(){
/*
Inside When parsing x in the scope chain, the active object of the local function is first searched. Of course, x is not found in this object.
However, since the active object inherits from Object.prototype, the next one of x is searched. The target is Object.prototype; and
Object.prototype does have the definition of x. As a result, the value of x is parsed as - outer, similar to the previous example,
contains x = 'inner'. The scope of the external function (active object) will not be resolved.
*/
alert(x); // Display: outer
})();
})();

But the amazing thing is that the variables in the function will even conflict with existing members of Object.prototype. Take a look at the following code:
Copy code The code is as follows:

(function(){
var constructor = function(){ return 1; };
(function(){
constructor(); // The evaluation result is {} (which is equivalent to calling Object.prototype.constructor()) instead of 1
constructor === Object.prototype.constructor; // true
toString === Object.prototype.toString; // true
// ……
})();
})();

To avoid this problem, avoid using property names in Object.prototype, such as toString, valueOf, hasOwnProperty, etc.

JScript solution
Copy code The code is as follows:

var fn = (function(){
// Declare a variable to reference the function
var f;
// Conditionally create a named function
// and assign its reference to f
if ( true) {
f = function F(){ }
}
else if (false) {
f = function F(){ }
}
else {
f = function F(){ }
}
// Declare a variable corresponding to the function name (identifier) ​​and assign it to null
// This is actually the function referenced by the corresponding identifier The object is marked,
// so that the garbage collector knows it can be recycled
var F = null;
// Returns a function defined according to the condition
return f;
}) ();

Finally we give an application example of applying the above technology, which is a cross-browser addEvent function code:
Copy the code The code is as follows:

// 1) Use an independent scope to include the statement
var addEvent = (function(){
var docEl = document.documentElement;
// 2) Declare the variable to reference the function
var fn;
if (docEl.addEventListener) {
// 3) Intentionally give the function a descriptive identifier
fn = function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
}
}
else if (docEl.attachEvent) {
fn = function addEvent(element, eventName, callback) {
element.attachEvent('on' eventName, callback);
}
}
else {
fn = function addEvent(element , eventName, callback) {
element['on' eventName] = callback;
}
}
// 4) Clear the addEvent function created by JScript
// Be sure to Use the var keyword before assignment
// unless addEvent has been declared at the top of the function
var addEvent = null;
// 5) Finally return the function referenced by fn
return fn;
} )();

Alternative
In fact, if we don’t want this descriptive name, we can do it in the simplest form, which is to declare a function inside the function ( instead of a function expression), and then return the function:
Copy the code The code is as follows:

var hasClassName = (function(){
// Define private variables
var cache = { };
// Use function declaration
function hasClassName(element, className) {
var _className = '(?:^|\s )' className '(?:\s |$)';
var re = cache[_className] || (cache[_className] = new RegExp(_className ));
return re.test(element.className);
}
// Return function
return hasClassName;
})();

Obviously, this solution will not work when there are multiple branch function definitions. However, there is a pattern that seems to be possible: that is, use function declarations to define all functions in advance, and specify different identifiers for these functions:
Copy code The code is as follows:

var addEvent = (function(){
var docEl = document.documentElement;
function addEventListener(){
/* . .. */
}
function attachEvent(){
/* ... */
}
function addEventAsProperty(){
/* ... */
}
if (typeof docEl.addEventListener != 'undefined') {
return addEventListener;
}
elseif (typeof docEl.attachEvent != 'undefined') {
return attachEvent;
}
return addEventAsProperty;
})();

Although this solution is elegant, it is not without its shortcomings. First, naming consistency is lost due to the use of different identifiers. Not to mention whether this is good or bad, at least it is not clear enough. Some people like to use the same name, but others don't care at all about the difference in wording. But after all, different names remind people of different implementations used. For example, when we see attachEvent in the debugger, we know that addEvent is based on the implementation of attachEvent. Of course, naming based on implementation may not always work. Suppose we want to provide an API and name the function inner in this way. Then API users can easily be confused by the details of the corresponding implementation.
To solve this problem, of course we have to think of a more reasonable naming scheme. But the key is not to cause any more trouble. The solutions I can think of now are as follows:
'addEvent', 'altAddEvent', 'fallbackAddEvent'
// or
'addEvent', 'addEvent2', 'addEvent3'
// Or
'addEvent_addEventListener', 'addEvent_attachEvent', 'addEvent_asProperty'
Copy code
In addition, this mode also has a small problem, that is, increased memory usage. Creating N functions with different names in advance means that N-1 functions will not be used. Specifically, if document.documentElement contains attachEvent, then addEventListener and addEventAsProperty are not needed at all. However, they all occupy memory; moreover, this memory will never be released, for the same reason as JScript's stinky named expressions - both functions are "trapped" in the closure of the returned function. Wrapped in the bag.
However, the issue of increased memory usage is really not a big deal. If a library - such as Prototype.js - adopts this pattern, it will only create one or two hundred more functions. As long as these functions are not created repeatedly (at runtime), but only once (at load time), then there's nothing to worry about.
WebKit’s displayName
The WebKit team has adopted a somewhat alternative strategy on this issue. Because anonymous and named functions have such poor expressiveness, WebKit introduces a "special" displayName attribute (essentially a string). If the developer assigns a value to this attribute of the function, the value of this attribute will be used during debugging. is displayed in place of the function "name" in the programmer or profiler. Francisco Tolmasky explains the principles and implementation of this strategy in detail.

Future considerations
The future version 5 of ECMAScript-262 (currently still a draft) will introduce the so-called strict mode. Enabling strict mode implementations disables features of the language that make them unstable, unreliable, and unsafe. It is said that for security reasons, the arguments.callee attribute will be "blocked" in strict mode. Therefore, while in strict mode, accessing arguments.callee results in a TypeError (see ECMA-262 5th Edition, Section 10.6). And the reason I mention strict mode here is because if you cannot use arguments.callee to perform recursive operations in an implementation based on the 5th edition standard, the possibility of using named function expressions increases significantly. In this sense, it is even more important to understand the semantics and bugs of named function expressions.

Copy code The code is as follows:

// Previously, you might use arguments.callee
(function(x) {
if (x <= 1) return 1;
return x * arguments.callee( x - 1);
})(10);
// But in strict mode, it is possible to use named function expressions
(function factorial(x) {
if (x <= 1) return 1;
return x * factorial(x - 1);
})(10);
// Or take a step back and use a less flexible function declaration
function factorial(x) {
if (x <= 1) return 1;
return x * factorial(x - 1);
}
factorial(10);

Acknowledgments
Richard Cornford, who was the first to explain the bug in named function expressions in JScript. Richard explains most of the bugs I mention in this post, so I highly recommend checking out his explanations. I would also like to thank Yann-Erwan Perio and Douglas Crockford for mentioning and discussing the NFE issue in the comp.lang.javascript forum back in 2003.
John-David Dalton has great suggestions for the "Final Solution".
Toby Langi’s idea was used by me in “The Alternative”.
Garrett Smith and Dmitry Soshnikov added and corrected various aspects of this article.

English original text: http://kangax.github.com/nfe/
Reference translation: Connection access (refer to this article for the chapters after SpiderMonkey’s Quirks)
Synchronization and recommendation
This article Synchronized to the directory index: In-depth understanding of JavaScript series
In-depth understanding of JavaScript series of articles, including original, translated, reprinted and other types of articles. If it is useful to you, please recommend and support it to give uncle the motivation to write.
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn