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 Date自定义函数 延迟脚本执行
Mar 10 Javascript
Date对象格式化函数代码
Jul 17 Javascript
Node.js中AES加密和其它语言不一致问题解决办法
Mar 10 Javascript
深入分析escape()、encodeURI()、encodeURIComponent()的区别及示例
Aug 04 Javascript
js去字符串前后空格的实现方法
Feb 26 Javascript
分离与继承的思想实现图片上传后的预览功能:ImageUploadView
Apr 07 Javascript
利用Js的console对象,在控制台打印调式信息测试Js的实现
Nov 26 Javascript
JS实现禁止用户使用Ctrl+鼠标滚轮缩放网页的方法
Apr 28 Javascript
mint-ui的search组件在键盘显示搜索按钮的实现方法
Oct 27 Javascript
微信小程序icon组件使用详解
Jan 31 Javascript
js中时间格式化的几种方法
Jul 22 Javascript
使用Node.js在深度学习中做图片预处理的方法
Sep 18 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二分法在IP地址查询中的应用
2008/08/12 PHP
PHP实现即时输出、实时输出内容方法
2015/05/27 PHP
使用PHP编写发红包程序
2015/07/22 PHP
关于PHP文件的自动运行方法分析
2016/05/13 PHP
Jquery 数组操作大全个人总结
2013/11/13 Javascript
js对象内部访问this修饰的成员函数示例
2014/04/27 Javascript
JavaScript设计模式之代理模式介绍
2014/12/28 Javascript
被遗忘的javascript的slice() 方法
2015/04/20 Javascript
javascript中闭包(Closure)详解
2016/01/06 Javascript
Angular2学习教程之组件中的DOM操作详解
2017/05/28 Javascript
微信小程序-滚动消息通知的实例代码
2017/08/03 Javascript
JavaScript基础进阶之数组方法总结(推荐)
2017/09/04 Javascript
一文让你彻底搞清楚javascript中的require、import与export
2017/09/24 Javascript
Vue-cropper 图片裁剪的基本原理及思路讲解
2018/04/17 Javascript
在Bootstrap开发框架中使用dataTable直接录入表格行数据的方法
2018/10/25 Javascript
微信小程序实现笑脸评分功能
2018/11/03 Javascript
jQuery实现消息弹出框效果
2019/12/10 jQuery
探究一道价值25k的蚂蚁金服异步串行面试题
2020/08/21 Javascript
python实现ping的方法
2015/07/06 Python
Python自动化运维之IP地址处理模块详解
2017/12/10 Python
TensorFlow实现Batch Normalization
2018/03/08 Python
python接口自动化(十六)--参数关联接口后传(详解)
2019/04/16 Python
Django如何实现网站注册用户邮箱验证功能
2019/08/14 Python
jupyter notebook 的工作空间设置操作
2020/04/20 Python
python实现数据结构中双向循环链表操作的示例
2020/10/09 Python
python实现自动打卡的示例代码
2020/10/10 Python
Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用?
2015/08/04 面试题
C++面试题:关于链表和指针
2013/06/05 面试题
GWT都有什么特性
2016/12/02 面试题
会计专业毕业生求职信分享
2014/01/03 职场文书
幼儿教师研修感言
2014/02/12 职场文书
房产继承公证书
2014/04/09 职场文书
2014县委书记党的群众路线教育实践活动对照检查材料思想汇报
2014/09/22 职场文书
2015暑期社会实践调查报告
2015/07/14 职场文书
Python基础之条件语句详解
2021/06/16 Python
JavaScript实现简单拖拽效果
2021/09/15 Javascript