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 相关文章推荐
javascript 获取表单file全路径
Dec 31 Javascript
javascript 全等号运算符使用说明
May 31 Javascript
jQuery 快速结束当前正在执行的动画
Nov 20 Javascript
jQuery Timelinr实现垂直水平时间轴插件(附源码下载)
Feb 16 Javascript
jquery动态遍历Json对象的属性和值的方法
Jul 27 Javascript
简单三步实现报表页面集成天气
Dec 15 Javascript
js实现点击按钮弹出上传文件的窗口
Dec 23 Javascript
妙用Angularjs实现表格按指定列排序
Jun 23 Javascript
ECMAscript 变量作用域总结概括
Aug 18 Javascript
对angular4子路由&辅助路由详解
Oct 09 Javascript
微信小程序3D轮播实现代码
Sep 19 Javascript
Echarts实现单条折线可拖拽效果
Dec 19 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
成本8450万,票房仅2亿,口碑两极分化,又一部DC电影扑街了
2020/04/09 欧美动漫
2.PHP入门
2006/10/09 PHP
PHP的ASP防火墙
2006/10/09 PHP
用PHP实现ODBC数据分页显示一例
2006/10/09 PHP
扩展你的 PHP 之入门篇
2006/12/04 PHP
PR值查询 | PageRank 查询
2006/12/20 PHP
php parse_str() 函数的定义和用法
2016/05/23 PHP
超级兔子让浮动层消失的前因后果
2007/03/09 Javascript
javascript 表单验证常见正则
2009/09/28 Javascript
使用javascipt---实现二分查找法
2013/04/10 Javascript
js/jquery解析json和数组格式的方法详解
2014/01/09 Javascript
js模仿php中strtotime()与date()函数实现方法
2015/08/11 Javascript
JavaScript实现上下浮动的窗口效果代码
2015/10/12 Javascript
微信分享调用jssdk实例
2017/06/08 Javascript
Vue2.0父组件与子组件之间的事件发射与接收实例代码
2017/09/19 Javascript
JavaScript设计模式之原型模式分析【ES5与ES6】
2018/07/26 Javascript
Vue 事件处理操作实例详解
2019/03/05 Javascript
JavaScript实现轮播图效果代码实例
2019/09/28 Javascript
微信小程序自定义菜单切换栏tabbar组件代码实例
2019/12/30 Javascript
[55:45]DOTA2上海特级锦标赛D组败者赛 Liquid VS COL第一局
2016/02/28 DOTA
Python用Bottle轻量级框架进行Web开发
2016/06/08 Python
利用django-suit模板添加自定义的菜单、页面及设置访问权限
2018/07/13 Python
Python对ElasticSearch获取数据及操作
2019/04/24 Python
解决Pycharm 包已经下载,但是运行代码提示找不到模块的问题
2019/08/31 Python
Python TKinter如何自动关闭主窗口
2020/02/26 Python
泰海淘:泰国king Power王权免税集团旗下跨境海淘综合型电商
2020/07/26 全球购物
营业员演讲稿
2013/12/30 职场文书
商场促销活动方案
2014/02/08 职场文书
房产委托公证书
2014/04/08 职场文书
护理专科毕业自荐信范文
2014/04/21 职场文书
庆元旦活动总结
2014/07/09 职场文书
2014年乡镇工会工作总结
2014/12/02 职场文书
淘宝好评语句大全
2014/12/31 职场文书
捐助倡议书
2015/01/19 职场文书
《最后一头战象》读后感:动物也有感情
2020/01/02 职场文书
Pytorch反向传播中的细节-计算梯度时的默认累加操作
2021/06/05 Python