英文
函数声明对应的英文名称为 Function Declaration
函数表达式对应的英文名称为 Function Expression
Function Statement 有时是 Function Declaration 的另一种说法。在 mozilla 中,Function Statement 是 Function Declaration 的一种拓展,使得 Function Declaration 语句可以在任何允许使用 statement(语句)的地方使用。 For example, a function statement can be nested within an if statement (Conditionally created functions)。但是 Function Statement 现在还不是标准,所以不建议应用在产品开发中。
定义函数的方式
JS 中,函数声明和函数表达式都是用来定义函数的。函数声明是独立的语法结构,是单独存在的。但是函数表达式是作为表达式的一部分存在的。
- 函数声明定义了一个具名的且不需要赋值的“函数变量”,它是独立的语法,只能用在全局和函数体内。类似于变量的声明,就像变量声明必须用
var/let/const
开头一样,函数声明也必须用function
开头。 - 函数表达式将函数定义为一个表达式的一部分(通常是赋值表达式)。函数表达式可以不命名,分为匿名函数表达式和具名函数表达式。
PS:表达式是由运算符和操作符构成,并产生运算结果的语法结构。也就是说表达式是一个单纯的运算过程,并且总是有返回值。
一个函数声明定义的函数,被赋予一个变量 add:
一个匿名函数的函数表达式,被赋予一个变量 add:
一个具名函数的函数表达式,被赋予一个变量 add:
区分函数声明和函数表达式最简单的办法是看 function
关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果 function
关键字是声明中的第一个词,那么这就是个函数声明,否则就是一个函数表达式。
函数调用
函数声明的调用:
函数表达式的调用:
|
函数声明的调用只需要用函数变量(定义以后就无法更改)即可。
函数表达式的调用分两种情况:
- 将函数表达式赋值的变量(该变量可更改)作为函数用
- 在函数表达式前后加上一对圆括号调用(IIEF)
函数声明和具名函数表达式的名称有什么区别?
函数名和被函数赋值的变量存在着差别。函数名不能被改变,而被赋值的变量却可以再分配,且具名函数表达式的函数名只能在函数体内使用,倘若在函数体外使用函数名将会导致错误。
另一方面,被函数赋值的变量仅仅受限于它被声明时的作用域。
函数声明时也同时创建了一个与函数名同名的变量。因此,与函数表达式不同,函数声明定义的函数的函数名能在函数体内外使用。
此处是函数声明,add 被绑定到了所在的作用域中
此处是函数表达式,add1 被绑定到了自身函数中而不是所在作用域中
提升是什么?
JS 引擎处理一段代码实际上分为两个阶段:编译阶段和执行阶段。
PS: JS 虽然不是一种编译语言,但是在执行前也需要先进行编译。只不过跟普通的编译语言不同的是 JS 是一边编译一边执行的。
其中变量和函数的声明发生在 编译阶段。其他操作如赋值等都发生在 执行阶段。
这意味着无论作用域中的声明出现在什么地方,都将在代码执行之前首先被处理。可以将这个过程形象地想象为所有的声明(变量和函数)都被“移动”到了各自作用域的最顶端,这个过程被称为 提升。
|
上面代码段的输出结果为function a() {}
,undefiend
,function b2() {}
JS 引擎处理上面代码的顺序可能是这样的:
- 声明变量
a
和b
, 并将它们赋值undefined
- 创建函数
a
的函数体,并将其赋值给变量a
- 执行
alert(a); alert(b);
- 创建函数
b2
,并将其赋值给变量b
注意:在IE8及IE8以下版本浏览器b2
在外部也是可见的,是因为浏览器对命名函数赋值表达式进行了错误的解析, 解析成两个函数 b
和 b2
那我们现在就知道,变量和函数声明都会提升。但是,当变量和函数声明的函数名相同时,谁的优先级会更高呢。
答案是看情况:
同样是一个 x
标识
- 如果在进行函数
function x() {};
声明的同时,只声明var x;
而不对x
进行赋值操作,那么此时函数声明优先级更高,函数声明会覆盖掉变量声明 - 如果在进行函数
function x() {};
声明的同时,不仅声明var x;
而且同时还对x
进行赋值操作,即var x = 1;
,那么此时,变量声明会被 保存 ,不会被覆盖。变量声明的优先级更高, 会覆盖掉函数声明。只有当函数声明及对函数的操作出现在变量声明之前时,函数声明才不会被覆盖。
|
|
|
这其实与前面讲的并不矛盾,解析及执行过程如下:
- 对变量 a 进行声明,并赋值为 undefined
- 将函数体赋值给变量 a
- 如果此时,调用a,那么就是调用的 a 函数
- 如果此时出现对 a 的初始化,那么函数声明就被覆盖了,以后每次调用 a,调用的都是变量a。
也就是说,函数声明比变量声明优先级高的规则是没有变的,只是在代码执行阶段,变量被重写了。
到底是用函数声明还是函数表达式?
在 JavaScript 中,函数是第一等对象,之所以这样说,是因为它不仅可以像对象一样拥有属性和方法,而且可普通对象不一样的是它还可以被调用执行。函数表达式能更直观地看出是创建了一个对象。
function add(a,b) {return a + b}var add = function(a,b) {return a + b};函数表达式的使用范围更广。函数声明语句并非真正的语句,JS 规范只是允许它们作为顶级语句。它们可以出现在全局代码中,或者内嵌在其他函数中,但它们不能出现在循环、条件判断,或者 try/cache/finally 以及 with 语句中。相比之下,函数表达式是更大意义上表达式的一部分。你可以创建匿名函数用作回调函数,作为其它对象的属性(这里只指定义函数,函数声明的函数名也可以这样用),用立即执行函数创建私有作用域等等,它们可以出现在代码的任何地方。
- 函数表达式不会出现函数提升,放在哪里就在哪里执行,不会像函数声明那样随意放置具有误导性。
- 函数声明不能放在 if 条件句中,在一些浏览器中无论条件为 false 还是 true,函数都会被定义。
但是匿名函数表达式存在一些问题:
- 可读性,可理解性较差,有时候一个描述性的名称可以让你的代码不言自明。
- 匿名函数在栈追踪中不会显示出有意义的名称,使得调试很困难,虽然可以用具名函数表达式,但是赋值具名函数表达式在 IE9 以下又无法正确执行(浏览器对命名函数赋值表达式进行错误的解析, 解析成两个函数,一个变量名,一个函数名)。
- 如果没有函数名,函数在调用自身的时候就只能用 过期 的
arguments.callee
。函数调用自身的例子有递归以及事件触发后事件监听器需要解绑自身。
因此,除了赋值命名函数表达式外,对函数表达式命名是很有必要的。
总结:大部分情况下,用函数表达式代替函数声明都是很好的实践,但是有时用函数声明定义函数也是很有必要的,比如要用到多次的函数。所以还是要根据具体情况决定到底用哪种方式去定义函数。
本文参考:
【1】https://javascriptweblog.wordpress.com/2010/07/06/function-declarations-vs-function-expressions/
【2】http://codingfishman.github.io/2016/05/29/%E5%87%BD%E6%95%B0%E5%A3%B0%E6%98%8E%E4%B8%8E%E5%87%BD%E6%95%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F/
【3】你不知道的 JavaScript(上卷)