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 相关文章推荐
原生JS绑定滑轮滚动事件兼容常见浏览器
Jun 30 Javascript
jQuery动画显示和隐藏效果实例演示(附demo源码下载)
Dec 31 Javascript
Bootstrap前端开发案例二
Jun 17 Javascript
js控住DOM实现发布微博效果
Aug 30 Javascript
JS实现的手机端精简幻灯片效果
Sep 05 Javascript
利用原生JS与jQuery实现数字线性变化的动画
Feb 24 Javascript
微信小程序城市定位的实现实例(获取当前所在国家城市信息)
May 17 Javascript
Node层模拟实现multipart表单的文件上传示例
Jan 02 Javascript
JavaScript设计模式之代理模式实例分析
Jan 16 Javascript
vue.js仿hover效果的实现方法示例
Jan 28 Javascript
微信小程序仿今日头条导航栏滚动解析
Aug 20 Javascript
vue-router懒加载的3种方式汇总
Feb 28 Vue.js
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
学习php设计模式 php实现适配器模式
2015/12/07 PHP
php简单备份与还原MySql的方法
2016/05/09 PHP
php微信开发之批量生成带参数的二维码
2016/06/26 PHP
php常用日期时间函数实例小结
2019/07/04 PHP
10个基于jQuery或JavaScript的WYSIWYG 编辑器整理
2010/05/06 Javascript
一个关于javascript匿名函数的问题分析
2012/03/30 Javascript
jquery的$getjson调用并获取远程的JSON字符串问题
2012/12/10 Javascript
jquery 显示*天*时*分*秒实现时间计时器
2014/05/07 Javascript
JS实现响应鼠标点击动画渐变弹出层效果代码
2016/03/25 Javascript
JS未跨域操作iframe里的DOM
2016/06/01 Javascript
jQuery替换节点用法示例(使用replaceWith方法)
2016/09/08 Javascript
详解JavaScript的闭包、IIFE、apply、函数与对象
2016/12/21 Javascript
javascript深拷贝的原理与实现方法分析
2017/04/10 Javascript
详解angular用$sce服务来过滤HTML标签
2017/04/11 Javascript
百度地图去掉marker覆盖物或者去掉maker的label文字方法
2018/01/26 Javascript
webpack手动配置React开发环境的步骤
2018/07/02 Javascript
webpack的CSS加载器的使用
2018/09/11 Javascript
jquery获取img的src值实例介绍
2019/01/16 jQuery
async/await优雅的错误处理方法总结
2019/01/30 Javascript
[02:57]DOTA2英雄基础教程 风行者
2014/01/16 DOTA
[02:03]完美世界DOTA2联赛10月30日赛事集锦
2020/10/31 DOTA
pyqt实现.ui文件批量转换为对应.py文件脚本
2019/06/19 Python
Python使用指定端口进行http请求的例子
2019/07/25 Python
python2.7实现复制大量文件及文件夹资料
2019/08/31 Python
tensorflow多维张量计算实例
2020/02/11 Python
Python扫描端口的实现
2021/01/25 Python
CSS3系列之3D制作方法案例
2017/08/14 HTML / CSS
法国体育用品商店:GO Sport
2019/10/23 全球购物
致200米运动员广播稿
2014/02/06 职场文书
交通安全寄语大全
2014/04/08 职场文书
医院搬迁方案
2014/06/14 职场文书
员工生日活动方案
2014/08/24 职场文书
中班上学期个人总结
2015/02/12 职场文书
Python实现列表拼接和去重的三种方式
2021/07/02 Python
动态规划之使用备忘录来改进Javascript函数
2022/04/07 Javascript
Win11怎么解除儿童账号限制?Win11解除微软儿童账号限制方法
2022/07/07 数码科技