JavaScript中函数声明与函数表达式的区别详解


Posted in Javascript onAugust 18, 2016

前言

在ECMAScript中,有两个最常用的创建函数对象的方法,即使用函数表达式或者使用函数声明。对此,ECMAScript规范明确了一点,即是,即函数声明 必须始终带有一个标识符(Identifier),也就是我们所说的函数名,而函数表达式则可以省略。下面看看这两者的详细区别介绍。

什么是 Function Declaration(函数声明)?

Function Declaration 可以定义命名的函数变量,而无需给变量赋值。Function Declaration 是一种独立的结构,不能嵌套在非功能模块中。可以将它类比为 Variable Declaration(变量声明)。就像 Variable Declaration 必须以“var”开头一样,Function Declaration 必须以“function”开头。

举例来说

function bar() {
 return 3;
}

ECMA 5(13.0)定义语法:

function Identifier ( FormalParameterList[opt] ) { FunctionBody }

函数名在自身作用域和父作用域内是可获取的(否则就取不到函数了)。

function bar() {
 return 3;
}
 
bar() //3
bar //function

什么是 Function Expression(函数表达式)?

Function Expression 将函数定义为表达式语句(通常是变量赋值)的一部分。通过 Function Expression 定义的函数可以是命名的,也可以是匿名的。Function Expression 不能以“function”开头(下面自调用的例子要用括号将其括起来)。

举例来说

//anonymous function expression
var a = function() {
 return 3;
}
 
//named function expression
var a = function bar() {
 return 3;
}
 
//self invoking function expression
(function sayHello() {
 alert("hello!");
})();

ECMA 5(13.0)定义语法:

function Identifieropt ( FormalParameterList[opt] ) { FunctionBody }

(这个定义感觉并不完整,因为它忽略了一个条件:外围语句是表达式,并且不以“function”开头)

函数名(如果有的话)在作用域外是不可获取的(与 Function Declaration 对比)。

那 Function Statement 是什么?

Function Statement 有时是 Function Declaration 的另一种说法。但是 kangax 指出,在 mozilla 中,Function Statement 是 Function Declaration 的一种拓展,使得 Function Declaration 语句可以在任何允许使用 statement(语句)的地方使用。但是 Function Statement 现在还不是标准,所以不建议应用在产品开发中。

下面我们从一些小测试开始。猜猜以下情况都会弹出什么结果?

题 1:

function foo(){
 function bar() {
 return 3;
 }
 return bar();
 function bar() {
 return 8;
 }
}
alert(foo());

题 2:

function foo(){
 var bar = function() {
 return 3;
 };
 return bar();
 var bar = function() {
 return 8;
 };
}
alert(foo());

题 3:

alert(foo());
function foo(){
 var bar = function() {
 return 3;
 };
 return bar();
 var bar = function() {
 return 8;
 };
}

题 4:

function foo(){
 return bar();
 var bar = function() {
 return 3;
 };
 var bar = function() {
 return 8;
 };
}
alert(foo());

如果你的答案不是8、3、3和 [Type Error: bar is not a function] 的话,就继续往下读吧……(即使答对了也要继续读哦)

现在来解释下前面的测试。

Question 1 用了 function declaration,也就是说它们 get hoisted(被提升)了……

等一下,什么是 Hoisting?

这里引用 Ben Cherry的话:“Function declaration和function variable(函数变量)通常会被 JavaScript 解释器移(‘hoisted')到当前作用域顶部”。

function declaration 被提升时,整个函数体都会随之提升,所以 Question 1 的代码经过解释器解释后是像这样运行的:

//**Simulated processing sequence for Question 1**
function foo(){
 //define bar once
 function bar() {
 return 3;
 }
 //redefine it
 function bar() {
 return 8;
 }
 //return its invocation
 return bar(); //8
}
alert(foo());

但是,我们经常被告诉说,return 语句后面的代码是运行不到的啊……

执行 JavaScript 过程中,有 Context(ECMA 5 将之分解为 LexicalEnvironment、VariableEnvironment 和 ThisBinding)和 Process(一系列按序调用的语句)两个概念。当程序进入执行域时,Declaration 会造成 VariableEnvironment。它们不同于 Statement(比如 return),也不遵循 Statement 的运行规则。

Function Expression 会被提升吗?

这取决于表达式。比如 Question 2 中的第一个表达式:

var bar = function() {
 return 3;
};

等号左边的(var bar)是 Variable Declaration。Variable Declaration 会被提升,但是 Assignment Expression(赋值表达式)不会。所以当 bar 提升时,解释器会这样初始化:var bar = undefined。而函数定义本身不会被提升。

(ECMA 5 12.2 带有 initialzier(初始化器)的变量是在 VariableStatement 执行时,由 AssignmentExpression 赋值的,而不是在变量被创建时。)

因此 Question 2 的代码会按以下顺序运行:

//**Simulated processing sequence for Question 2**
function foo(){
 //a declaration for each function expression
 var bar = undefined;
 var bar = undefined;
 //first Function Expression is executed
 bar = function() {
 return 3;
 };
 // Function created by first Function Expression is invoked
 return bar();
 // second Function Expression unreachable
}
alert(foo()); //3

你可能会说,这还能解释的通,但是 Question 3 的答案错了,我在 Firebug 运行会报错。

把代码保存在 HTML 文件中,之后在 Firefox 上运行试试。或者在 IE8、Chrome 或 Safari 控制台中运行。显然 Firebug 控制台在“global(全局)”作用域(实际并不是全局的,而是特有的“Firebug”作用域——试着在 Firebug 控制台中运行“this == window”你就知道了)运行代码时,不会将函数提升。

Question 3 和 Question 1 的逻辑相似。这次是 foo 函数被提升了。

Question 4 就很简单了,根本就没有函数提升……

可以这么说,但是如果根本没有提升的话,TypeError 会是“bar not defined”,而不是“bar not a function”。此例中确实没有函数提升,但是有变量提升。因此 bar 在开始就被声明了,但是它的值并没有定义。其它代码都是按顺序执行的。

//**Simulated processing sequence for Question 4**
function foo(){
 //a declaration for each function expression
 var bar = undefined;
 var bar = undefined;
 return bar(); //TypeError: "bar not defined"
 //neither Function Expression is reached
}
alert(foo());

还应该注意什么?

官方是禁止在非功能模块(比如 if)中使用 Function Declaration 的。但是所有浏览器都支持,但是各自的解释方式不同。

例如下面的代码段在 Firefox 3.6 中会抛错,因为它将 Function Declaration 解释成了 Function Statement(见上文),所以 x 没有定义。但是在 IE8、Chrome 5 和 Safari 5 中,会返回函数 x(和标准的 Function Declaration 一样)。

function foo() {
 if(false) {
 function x() {};
 }
 return x;
}
alert(foo());

可以看出使用 Function Declaration 可能会引起混淆,那么它有什么优点吗?

你可能会说 Function Declaration 很宽松啊——如果试图在声明前使用函数,提升确实可以修正顺序,以便函数可以正确调用。但是这种宽松不利于严谨的编码,从长远的角度来看,很有可能会促进而不是阻止意外的发生。毕竟,程序员按特定的顺序排列语句是有原因的。

那么还有其它理由支持 Function Expression 的吗?

你猜呢?

1)Function Declaration 感觉像是要模仿 Java 风格的方法声明,但是 Java 方法和 JavaScript 并不一样。在 JavaScript 中,函数是含值的 living 对象。Java 方法仅是对元数据的存储。下面的两段代码都定义了函数,但是只有 Function Expression 看着像创建了对象。

//Function Declaration
function add(a,b) {return a + b};
//Function Expression
var add = function(a,b) {return a + b};

2)Function Expression 用处更多。Function Declaration 只能作为“statement”孤立存在。它所能做的就是创建一个当前作用域下的对象变量。相比之下,Function Expression(根据定义)是大型结构的一部分。如果想要创建匿名函数、给 prototype(原型)添加函数或是将函数用作其它对象的 property(属性),都可以用 Function Expression。每当用高阶应用,比如 curry 或 compose,创建新的函数时都是在用 Function Expression。Function Expression 和 Functional Programming(函数式编程)分不开。

//Function Expression
var sayHello = alert.curry("hello!");

Function Expression 有缺点吗?

Function Expression 创建的函数大多是匿名的。比如下面的函数是匿名的,today 只是一个匿名函数的引用:

var today = function() {return new Date()}

这会有问题吗?多数情况下不会,但是就像 Nick Fitzgerald 指出的,调试匿名函数会很烦。他建议使用 Named Function Expressions (NFEs)作为工作区:

var today = function today() {return new Date()}

但是如 Asen Bozhilov 所说(和 Kangax 文档)NFEs 在 IE9 以下无法正确执行。

结论

随意放置的 Function Declaration 具有误导性,并且很少有(如果有的话)情况,用 Function Expression 给变量赋值无法替代 Function Declaration。但是如果必须使用 Function Declaration 的话,将其放在所属作用域顶部可以减少混淆。永远不要把 Function Declaration 放在 if 语句中。

说了这么多,可能在你自己的情况下,Function Declaration 还是很有用的。这没什么。死记教条是危险的,并且通常会造成代码拐弯抹角。更重要的是你理解了概念,这样就可以根据自身情况决定用哪种方式创建函数。以上就是本文的全部内容了,希望此文对大家在这方面有帮助。

Javascript 相关文章推荐
JQuery 网站换肤功能实现代码
Nov 02 Javascript
jQuery之折叠面板的深入解析
Jun 19 Javascript
jQuery图片切换插件jquery.cycle.js使用示例
Jun 16 Javascript
node.js中的fs.readSync方法使用说明
Dec 17 Javascript
JS获得选取checkbox整行数据的方法
Jan 28 Javascript
JS常用字符串方法(推荐)
Jan 15 Javascript
避免jQuery名字冲突 noConflict()方法
Jul 30 Javascript
js实现图片加载淡入淡出效果
Apr 07 Javascript
JavaScript使用ZeroClipboard操作剪切板
May 10 Javascript
p5.js入门教程和基本形状绘制
Mar 15 Javascript
详解jenkins自动化部署vue
May 14 Javascript
vue 实现路由跳转时更改页面title
Nov 05 Javascript
Javascript中apply、call、bind的巧妙使用
Aug 18 #Javascript
AngularJS入门教程之双向绑定详解
Aug 18 #Javascript
AngularJS入门教程之迭代器过滤详解
Aug 18 #Javascript
AngularJS入门教程之AngularJS 模板
Aug 18 #Javascript
AngularJS入门教程之静态模板详解
Aug 18 #Javascript
AngularJS入门教程引导程序
Aug 18 #Javascript
Bootstrap模态框(modal)垂直居中的实例代码
Aug 18 #Javascript
You might like
动漫定律:眯眯眼都是怪物!这些角色狠话不多~
2020/03/03 日漫
PHP去除数组中重复的元素并按键名排序函数
2008/08/18 PHP
php结合飞信 免费天气预报短信
2009/05/07 PHP
比较discuz和ecshop的截取字符串函数php版
2012/09/03 PHP
去除php注释和去除空格函数分享
2014/03/13 PHP
PhpDocumentor 2安装以及生成API文档的方法
2014/05/21 PHP
PHP使用PDO访问oracle数据库的步骤详解
2017/09/29 PHP
JavaScript iframe的相互操作浅析
2009/10/14 Javascript
浅析node.js中close事件
2014/11/26 Javascript
浅谈javascript回调函数
2014/12/07 Javascript
JavaScript中数组slice和splice的对比小结
2016/09/22 Javascript
bootstrap+jQuery实现的动态进度条功能示例
2017/05/25 jQuery
jquery dataTable 后台加载数据并分页实例代码
2017/06/07 jQuery
js实现1,2,3,5数字按照概率生成
2017/09/12 Javascript
JS实现小球的弹性碰撞效果
2017/11/11 Javascript
Angular6 正则表达式允许输入部分中文字符
2018/09/10 Javascript
深入浅析Node.js 事件循环、定时器和process.nextTick()
2018/10/22 Javascript
JS实现密码框效果
2020/09/10 Javascript
详解Vue中的自定义指令
2020/12/07 Vue.js
[03:49]显微镜下的DOTA2第十五期—VG登基之路完美团
2014/06/24 DOTA
python3 实现一行输入,空格隔开的示例
2018/11/14 Python
pytorch 转换矩阵的维数位置方法
2018/12/08 Python
python获取本机所有IP地址的方法
2018/12/26 Python
解决Python中pandas读取*.csv文件出现编码问题
2019/07/12 Python
在python中用print()输出多个格式化参数的方法
2019/07/16 Python
pytorch之添加BN的实现
2020/01/06 Python
Python基础之列表常见操作经典实例详解
2020/02/26 Python
MATCHESFASHION.COM美国官网:英国奢侈品零售商
2018/10/29 全球购物
静态变量和实例变量的区别
2015/07/07 面试题
爱情寄语大全
2014/04/09 职场文书
学生请假条格式
2014/04/11 职场文书
活动总结报告怎么写
2014/07/03 职场文书
不服从公司安排检讨书
2014/09/24 职场文书
让生命充满爱观后感
2015/06/08 职场文书
话题作文之学会尊重
2019/12/16 职场文书
解决Python保存文件名太长OSError: [Errno 36] File name too long
2022/05/11 Python