1. Function declaration and function expression
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 makes one point clear: function declarations must have identifiers (Identifiers). ) (which is what everyone often calls the function name), and this identifier can be omitted in function expressions:
Function declaration: function function name (parameters: optional){function body}
Function expression: function function name (optional) (parameters: optional) { function body }
So, it can be seen that if the function name is not declared, it must be an expression. But if the 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.
function foo(){} // 声明,因为它是程序的一部分 var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分 new function bar(){}; // 表达式,因为它是new表达式 (function(){ function bar(){} // 声明,因为它是函数体的一部分 })();
There is a very subtle difference between expressions and declarations. First of all, function declarations will be parsed and evaluated before any expressions are parsed and evaluated. Even if your declaration is on the last line of code, it will be The first expression in the same scope is parsed/evaluated before. Refer to the following example. The function fn is declared after alert, but when alert is executed, fn is already defined:
alert(fn()); function fn() { return 'Hello world!'; }
In addition, there is another point that needs to be reminded. Although function declarations can be used within conditional statements, they have not been standardized. This means that different environments may have different execution results, so in this case, it is best to use function expressions. Formula: Because there is no concept of block-level scope in conditional statements
// 千万别这样做! // 因为有的浏览器会返回first的这个function,而有的浏览器返回的却是第二个 if (true) { function foo() { return 'first'; } } else { function foo() { return 'second'; } } foo(); // 相反,这样情况,我们要用函数表达式 var foo; if (true) { foo = function() { return 'first'; }; } else { foo = function() { return 'second'; }; } foo();
The actual rules for function declaration are as follows:
Function declaration can only appear in the 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 way an expression can 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).
2. Named function expression
When it comes to 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 only Valid within the scope of the newly defined function, because the specification stipulates that identifiers cannot be valid within the surrounding scope:
var f = function foo(){ return typeof foo; // function --->foo是在内部作用域内有效 }; // foo在外部用于是不可见的 typeof foo; // "undefined" f(); // "function"
Since this is required, 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.
tips:Here is a small question: in ES3, the scope object of the named function expression also inherits the properties of Object.prototype. This means that simply naming the function expression will also bring all properties from Object.prototype into scope. The results may be surprising.
var constructor = function(){return null;} var f = function f(){ return construcor(); } f(); //{in ES3 环境}
This program looks like it will produce null, but it actually produces a new object. Because a named function expression inherits Object.prototype.constructor (that is, the constructor of Object) in its scope. Just like the with statement, this scope will be affected by dynamic changes to Object.prototype. Fortunately, ES5 fixes this bug.
A reasonable solution to this behavior is to create a local variable with the same name as the function expression and assign it a value of null. Even in environments that do not incorrectly hoist a function expression declaration, redeclaring a variable with var ensures that the variable g is still bound. Setting the variable g to null ensures that duplicate functions can be garbage collected.
var f = function g(){ return 17; } var g =null;
3、调试器(调用栈)中的命名函数表达式
刚才说了,命名函数表达式的真正用处是调试,那到底怎么用呢?如果一个函数有名字,那调试器在调试的时候会将它的名字显示在调用的栈上。有些调试器(Firebug)有时候还会为你们函数取名并显示,让他们和那些应用该函数的便利具有相同的角色,可是通常情况下,这些调试器只安装简单的规则来取名,所以说没有太大价值,我们来看一个例子:不用命名函数表达式
function foo(){ return bar(); } function bar(){ return baz(); } function baz(){ debugger; } foo(); // 这里我们使用了3个带名字的函数声明 // 所以当调试器走到debugger语句的时候,Firebug的调用栈上看起来非常清晰明了 // 因为很明白地显示了名称 baz bar foo expr_test.html()
通过查看调用栈的信息,我们可以很明了地知道foo调用了bar, bar又调用了baz(而foo本身有在expr_test.html文档的全局作用域内被调用),不过,还有一个比较爽地方,就是刚才说的Firebug为匿名表达式取名的功能:
function foo(){ return bar(); } var bar = function(){ return baz(); } function baz(){ debugger; } foo(); // Call stack baz bar() //看到了么? foo expr_test.html()
然后,当函数表达式稍微复杂一些的时候,调试器就不那么聪明了,我们只能在调用栈中看到问号:
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 (?)() // 这里可是问号哦,显示为匿名函数(anonymous function) foo expr_test.html()
另外,当把函数赋值给多个变量的时候,也会出现令人郁闷的问题:
function foo(){ return baz(); } var bar = function(){ debugger; }; var baz = bar; bar = function() { alert('spoofed'); }; foo(); // Call stack: bar() foo expr_test.html()
这时候,调用栈显示的是foo调用了bar,但实际上并非如此,之所以有这种问题,是因为baz和另外一个包含alert(‘spoofed')的函数做了引用交换所导致的。
归根结底,只有给函数表达式取个名字,才是最委托的办法,也就是使用命名函数表达式。我们来使用带名字的表达式来重写上面的例子(注意立即调用的表达式块里返回的2个函数的名字都是bar):
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(); // 又再次看到了清晰的调用栈信息了耶! baz bar foo expr_test.html()
好的,整个文章结束,大家对javascript的认识又近了一步,希望大家越来越喜欢小编为大家整理的文章,继续关注跟我学习javascript的一系列文章。