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

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

复制代码 代码如下:

//下述两种声明方式有什么不同
 
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
}
};

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

时间: 2024-09-30 18:56:56

JavaScript中的函数声明和函数表达式区别浅析_javascript技巧的相关文章

Javascript 中 null、NaN和undefined的区别总结_javascript技巧

1.类型分析: js中的数据类型有undefined,boolean,number,string,object等5种,前4种为原始类型,第5种为引用类型. 代码 复制代码 代码如下: var a1; var a2 = true; var a3 = 1; var a4 = "Hello"; var a5 = new Object(); var a6 = null; var a7 = NaN; var a8 = undefined; alert(typeof a); //显示"u

javascript 中String.match()与RegExp.exec()的区别说明_javascript技巧

1. 这两个方法,如果匹配成功,返回一个数组,匹配失败,返回null. 2. 当RegExp的global属性为false时,这两个方法的返回数组是一样的. 数组的第0个元素是整个pattern的第一个匹配字符串,接下来的元素是pattern第一个匹配中的子匹配字符串. 此外,数组还有index和input两个额外属性,index是匹配字符串的起始位置,input是整个输入字符串. 此时,RegExp的lastIndex属性一直是0. demo: 复制代码 代码如下: var s = 'this

javascript函数定义的几种区别小结_javascript技巧

javascript函数的定义 1:调用关键字function来构造,如:function distance(x1,x2,y1,y2){var dx=x2-x1;var dy=y2-y1;return Math.sqrt(dx*dx+dy*dy);} 2:使用Function()构造函数var f=new Function*"x","y","return x*y");这行代码创建了一个新函数,该函数和你所熟悉的语法定义的函数基本上时等价的: fu

JavaScript整除运算函数ceil和floor的区别分析_javascript技巧

本文实例分析了JavaScript整除运算函数ceil和floor的区别.分享给大家供大家参考.具体分析如下: Math.ceil(count / pagesize); //向上整除,如 Math.ceil(4/3)结果为2; Math.floor(count / pagesize); //向下整除,如 Math.floor(4/3)结果为1; 希望本文所述对大家的javascript程序设计有所帮助. 以上是小编为您精心准备的的内容,在的博客.问答.公众号.人物.课程等栏目也有的相关内容,欢迎

JavaScript中的new的使用方法与注意事项_javascript技巧

原文: JavaScript, We Hardly new Ya  --Douglas Crockford.    http://yuiblog.com/blog/2006/11/13/javascript-we-hardly-new-ya/ 引用 JavaScript是一门基于原型的语言,但它却拥有一个 new 操作符使得其看起来象一门经典的面对对象语言.那样也迷惑了程序员们,导致一些有问题的编程模式. 其实你永远不需要在JavaScript使用 new Object().用字面量的形式{}去

JavaScript中如何通过arguments对象实现对象的重载_javascript技巧

复制代码 代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script type="text/javascript"> /* *1.js 中不存在函数的重载 2.js函数定义时候的形参个数,和执行时候时,传递的实参的个数可以不一样. 3.js执行时

javascript中break,continue和return语句用法小结_javascript技巧

由于最近一直在看javascript,下面简要说一下它们三个在javascript的一些用法 break语句: break语句会使运行的程序立刻退出包含在最内层的循环或者退出一个switch语句.由于它是用来退出循环或者switch语句,所以只有当它出现在这些语句时,这种形式的break语句才是合法的. 如果一个循环的终止条件非常复杂,那么使用break语句来实现某些条件比用一个循环表达式来表达所有的条件容易得多. [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行] continue语句

浅析JavaScript中的变量复制、参数传递和作用域链_javascript技巧

今天在看书的过程中,又发现了自己目前对Javascript存在的一个知识模糊点:JS的作用域链,所以就通过查资料看书对作用域链相关的内容进行了学习.今天学习笔记主要有这样几个关键字:变量.参数传递.执行环境.变量对象.作用域链. 1.变量 变量需要注意的有两点:变量声明和复制变量值. 变量声明肯定大家都很熟悉,在JS中我们都是通过 var 关键字进行变量声明的.JS中规定,通过var声明的变量会被添加到最近的环境中,如果声明并且初始化一个变量没有用到var关键字,这个变量会被添加到全局环境中.

javascript中的变量作用域以及变量提升详细介绍_javascript技巧

变量作用域"一个变量的作用域表示这个变量存在的上下文.它指定了你可以访问哪些变量以及你是否有权限访问某个变量." 变量作用域分为局部作用域和全局作用域. 局部变量(处于函数级别的作用域)不像其他对面对象的编程语言(比方说C++,Java等等),javascript没有块级作用域(被花括号包围的):当是,javascript有拥有函数级别的作用域,也就是说,在一个函数内定义的变量只能在函数内部访问或者这个函数内部的函数访问(闭包除外,这个我们过几天再写个专题). 函数级别作用域的一个例子