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 相关文章推荐
js获取当前select 元素值的代码
Apr 19 Javascript
js 禁止选择功能实现代码(兼容IE/Firefox)
Apr 23 Javascript
JQuery动画和停止动画实例代码
Mar 01 Javascript
Extjs4 GridPanel的主要配置参数详细介绍
Apr 18 Javascript
用js实现输入提示(自动完成)的实例代码
Jun 14 Javascript
JavaScript中使用typeof运算符需要注意的几个坑
Nov 08 Javascript
jQuery中first()方法用法实例
Jan 06 Javascript
thinkphp 表名 大小写 窍门
Feb 01 Javascript
浅谈js script标签中的预解析
Dec 30 Javascript
React-Native中禁用Navigator手势返回的示例代码
Sep 09 Javascript
Node.JS中快速扫描端口并发现局域网内的Web服务器地址(80)
Sep 18 Javascript
在Vue里如何把网页的数据导出到Excel的方法
Sep 30 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 array_flip() 删除重复数组元素专用函数
2010/05/16 PHP
Laravel 4 初级教程之安装及入门
2014/10/30 PHP
Yii2框架实现数据库常用操作总结
2017/02/08 PHP
详解Yii2.0 rules验证规则集合
2017/03/21 PHP
完美解决在ThinkPHP控制器中命名空间的问题
2017/05/05 PHP
PHP实现通过strace定位故障原因的方法
2018/04/29 PHP
PHP Include文件实例讲解
2019/02/15 PHP
thinkphp5实现微信扫码支付
2019/12/23 PHP
javascript制作网页图片上实现下雨效果
2015/02/26 Javascript
JS实现方形抽奖效果
2018/08/27 Javascript
JavaScript 链表定义与使用方法示例
2020/04/28 Javascript
typescript配置alias的详细步骤
2020/08/12 Javascript
Javascript如何实现扩充基本类型
2020/08/26 Javascript
[52:40]完美世界DOTA2联赛PWL S2 Magma vs GXR 第一场 11.29
2020/12/02 DOTA
python局域网ip扫描示例分享
2014/04/03 Python
python装饰器初探(推荐)
2016/07/21 Python
idea创建springMVC框架和配置小文件的教程图解
2018/09/18 Python
python画图系列之个性化显示x轴区段文字的实例
2018/12/13 Python
Python二叉树的遍历操作示例【前序遍历,中序遍历,后序遍历,层序遍历】
2018/12/24 Python
详解python--模拟轮盘抽奖游戏
2019/04/12 Python
Python图像处理之图片文字识别功能(OCR)
2019/07/30 Python
Django之使用内置函数和celery发邮件的方法示例
2019/09/16 Python
Python模块/包/库安装的六种方法及区别
2020/02/24 Python
150行Python代码实现带界面的数独游戏
2020/04/04 Python
解决运行出现'dict' object has no attribute 'has_key'问题
2020/07/15 Python
python openCV实现摄像头获取人脸图片
2020/08/20 Python
如何用border-image实现文字气泡边框的示例代码
2020/01/21 HTML / CSS
Sam’s Club山姆会员商店:沃尔玛旗下高端会员制商店
2017/01/16 全球购物
施华洛世奇巴西官网:SWAROVSKI巴西
2019/12/03 全球购物
合作协议书格式
2014/08/19 职场文书
新教师培训心得体会
2014/09/02 职场文书
2014年乡镇妇联工作总结
2014/12/02 职场文书
2015年节能减排工作总结
2015/05/14 职场文书
活动新闻稿范文
2015/07/17 职场文书
九年级语文教学反思
2016/03/03 职场文书
Mongodb 迁移数据块的流程介绍分析
2022/04/18 MongoDB