JavaScript中的函数声明和函数表达式区别浅析


Posted in Javascript onMarch 27, 2015

记得在面试腾讯实习生的时候,面试官问了我这样一道问题。

//下述两种声明方式有什么不同

 

function foo(){};

 

var bar = function foo(){}; 

当初只知道两种声明方式一个是函数声明一个是函数表达式,具体有什么不同没能说得很好。最近正好看到这方面的书籍,就想好好总结一番。

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

函数声明:

function Identifier ( FormalParameterList opt){ FunctionBody }

函数声明解析过程如下:

1. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。将当前正在运行环境中作用域链作为它的作用域。

2. 为当前变量对象创建一个名为Identifier的属性,值为Result(1)。

函数表达式:

(函数表达式分为匿名和具名函数表达式)

function Identifier opt( FormalParameterList opt){ FunctionBody }  //这里是具名函数表达式

具名函数表达式的解析过程如下:

1. 创建一个new Object对象
2. 将Result(1)添加到作用域链的顶端
3. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。将当前正在运行的执行环境中作用域链作为它的作用域。
4. 为Result(1)创建一个名为Identifier 的属性,其值为为Result(3),只读,不可删除
5. 从作用域链中移除Result(1)
6. 返回Result(3)

官方文档读起来十分拗口。简单来说,ECMAScript是通过上下文来区分这两者的:假如 function foo(){} 是一个赋值表达式的一部分,则认为它是一个函数表达式。而如果 function foo(){} 被包含在一个函数体内,或者位于程序(的最上层)中,则将它作为一个函数声明来解析。显然,在省略标识符的情况下,“表达式” 也就只能是表达式了。

function foo(){}; // 声明,因为它是程序的一部分

 

var bar = function foo(){}; // 表达式,因为它是赋值表达(AssignmentExpression)的一部分

 

new function bar(){}; // 表达式,因为它是New表达式(NewExpression)的一部分

 

(function(){

    function bar(){}; // 声明,因为它是函数体(FunctionBody)的一部分

})();

还有一种情况:

(function foo(){})

这种情况也是函数表达式,它被包含在一对圆括号中的函数,在其上下文环境中,()构成了一个分组操作符,而分组操作符只能包含表达式,更多的例子:

function foo(){}; // 函数声明

 

(function foo(){}); // 函数表达式:注意它被包含在分组操作符中

 

try {

(var x = 5); // 分组操作符只能包含表达式,不能包含语句(这里的var就是语句)

}

catch(err) {

// SyntaxError(因为“var x = 5”是一个语句,而不是表达式——对表达式求值必须返回值,但对语句求值则未必返回值。——译

}

下面简单说说函数声明与函数表达式的异同。声明和表达式的行为存在着十分微妙而又十分重要的差别。

首先,函数声明会在任何表达式被解析和求值之前先行被解析和求值。即使声明位于源代码中的最后一行,它也会先于同一作用域中位于最前面的表达式被求值。还是看个例子更容易理解。在下面这个例子中,函数 fn 是在 alert 后面声明的。但是,在alert 执行的时候,fn已经有定义了:

alert(fn()); //输出Helloworld!   

 

function fn() {

return 'Helloworld!';

}


简单总结,区别在什么地方呢?

1. 声明总是在作用域开始时先行解析;
2. 表达式在遇到时候才运算。

函数声明还有另外一个重要的特点,即通过条件语句控制函数声明的行为并未标准化,因此不同环境下可能会得到不同的结果。即是:

// 千万不要这样做!

// 不同浏览器会有不同返回结果,

 

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();

 那么,使用函数声明的实际规则到底是什么? 

FunctionDeclaration(函数声明)只能出现在Program(程序)或FunctionBody(函数体)内。从句法上讲,它们 不能出现在Block(块)({ ... })中,例如不能出现在 if、while 或 for 语句中。因为 Block(块) 中只能包含Statement(语句), 而不能包含FunctionDeclaration(函数声明)这样的SourceElement(源元素)。

另一方面,仔细看一看产生规则也会发现,唯一可能让Expression(表达式)出现在Block(块)中情形,就是让它作为ExpressionStatement(表达式语句)的一部分。但是,规范明确规定了ExpressionStatement(表达式语句)不能以关键字function开头。而这实际上就是说,FunctionExpression(函数表达式)同样也不能出现在Statement(语句)或Block(块)中(别忘了Block(块)就是由Statement(语句)构成的)。

由于存在上述限制,只要函数出现在块中(像上面例子中那样),实际上就应该将其看作一个语法错误,而不是什么函数声明或表达式。

那么我们应该在什么时候使用函数声明或函数表达式呢?函数声明只能出现在“程序代码”中,意味着只能在其它函数体中或者全局空间;它们的定义不能不能赋值给一个变量或属性,或者作为一个参数传递出现在函数调用中;下面的例子是函数声明的允许的用法,foo(),bar()和local()都是通过函数声明模式声明:

// 全局环境

function foo() {} 

 

function local() { 

// 局部环境 

    function bar() {} 

        return bar; 

}

当你在语法上不能使用函数声明的时候,你就可以使用函数表达式。比如:传递一个函数作为参数或者在对象字面量中定义一个函数:

// 这是一个匿名函数表达式

callMe(function () { 

 

//传递一个函数作为参数

}); 

 

// 这是一个具名函数表达式

callMe(function me() { 

  

// 传递一个函数作为参数,函数名为me

}); 

 

// 其他函数表达式

var myobject = { 

    say: function () { 

 

// I am a function expression 

} 

};

学识有限,如有错误,欢迎指正。

Javascript 相关文章推荐
Ruffy javascript 学习笔记
Nov 30 Javascript
解决js正则匹配换行问题实现代码
Dec 10 Javascript
DOM基础教程之使用DOM控制表单
Jan 20 Javascript
jQuery选择器源码解读(四):tokenize方法的Expr.preFilter
Mar 31 Javascript
整理AngularJS中的一些常用指令
Jun 16 Javascript
jquery mobile开发常见问题分析
Jan 21 Javascript
Vue.js路由vue-router使用方法详解
Mar 20 Javascript
Angular.js中定时器循环的3种方法总结
Apr 27 Javascript
详解用Node.js实现Restful风格webservice
Sep 29 Javascript
js 原生判断内容区域是否滚动到底部的实例代码
Nov 15 Javascript
JS数组属性去重并校验重复数据
Jan 10 Javascript
vue 判断两个时间插件结束时间必选大于开始时间的代码
Nov 04 Javascript
jQuery使用fadein方法实现渐出效果实例
Mar 27 #Javascript
jQuery使用fadeout实现元素渐隐效果的方法
Mar 27 #Javascript
javascript包装对象实例分析
Mar 27 #Javascript
javascript操作select元素实例分析
Mar 27 #Javascript
jquery使用slideDown实现模块缓慢拉出效果的方法
Mar 27 #Javascript
jQuery使用slideUp方法实现控制元素缓慢收起
Mar 27 #Javascript
jquery使用animate方法实现控制元素移动
Mar 27 #Javascript
You might like
php环境配置之CGI、FastCGI、PHP-CGI、PHP-FPM、Spawn-FCGI比较?
2011/10/17 PHP
php 操作数组(合并,拆分,追加,查找,删除等)
2012/07/20 PHP
慎用preg_replace危险的/e修饰符(一句话后门常用)
2013/06/19 PHP
php获取开始与结束日期之间所有日期的方法
2016/11/29 PHP
PHP基于GD2函数库实现验证码功能示例
2019/01/27 PHP
JQquery的一些使用心得分享
2012/08/01 Javascript
JS创建类和对象的两种不同方式
2014/08/08 Javascript
ECMA5数组的新增方法有哪些及forEach()模仿实现
2015/11/03 Javascript
JavaScipt中栈的实现方法
2016/02/17 Javascript
jQuery实现漂亮实用的商品图片tips提示框效果(无图片箭头+阴影)
2016/04/16 Javascript
基于JavaScript+HTML5 实现打地鼠小游戏逻辑流程图文详解(附完整代码)
2017/11/02 Javascript
浅析微信扫码登录原理(小结)
2018/10/29 Javascript
VUE脚手架具体使用方法
2019/05/20 Javascript
layer.open 获取不到表单信息的解决方法
2019/09/26 Javascript
jQuery实现简单三级联动效果
2020/09/05 jQuery
JS如何监听div的resize事件详解
2020/12/03 Javascript
Python的Django框架下管理站点的基本方法
2015/07/17 Python
Python 实现文件的全备份和差异备份详解
2016/12/27 Python
Python有序字典简单实现方法示例
2017/09/28 Python
Python排序搜索基本算法之归并排序实例分析
2017/12/08 Python
Python subprocess模块详细解读
2018/01/29 Python
PyTorch快速搭建神经网络及其保存提取方法详解
2018/04/28 Python
python编写猜数字小游戏
2019/10/06 Python
python怎么提高计算速度
2020/06/11 Python
PyCharm安装PyQt5及其工具(Qt Designer、PyUIC、PyRcc)的步骤详解
2020/11/02 Python
python实现企业微信定时发送文本消息的实例代码
2020/11/25 Python
瑞典领先的汽车零部件网上零售商:bildelaronline24.se
2017/01/12 全球购物
世界上第一个水枕头:Mediflow
2018/12/06 全球购物
医学检验专业大学生求职信
2013/11/18 职场文书
给酒店员工的表扬信
2014/01/11 职场文书
市级三好学生事迹材料
2014/08/27 职场文书
销售经理岗位职责范本
2015/04/02 职场文书
2015年语文教学工作总结
2015/05/25 职场文书
2015大学迎新标语
2015/07/16 职场文书
关于JavaScript轮播图的实现
2021/11/20 Javascript
mysql下的max_allowed_packet参数设置详解
2022/02/12 MySQL