我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐 藏”起来,外部作用域无法访问包装函数内部的任何内容。

例如:

var a = 2;
function foo() { // <-- 添加这一行
var a = 3;
console.log( a ); // 3
} // <-- 以及这一行
foo(); // <-- 以及这一行
console.log( a ); // 2

虽然这种技术可以解决一些问题,但是它并不理想,因为会导致一些额外的问题。首先, 必须声明一个具名函数 foo(),意味着 foo 这个名称本身“污染”了所在作用域(在这个 例子中是全局作用域)。其次,必须显式地通过函数名(foo())调用这个函数才能运行其 中的代码。

如果函数不需要函数名(或者至少函数名可以不污染所在作用域),并且能够自动运行, 这将会更加理想。

幸好,JavaScript 提供了能够同时解决这两个问题的方案、

var a = 2;
(function foo(){ // <-- 添加这一行
var a = 3;
console.log( a ); // 3
})(); // <-- 以及这一行
console.log( a ); // 2

接下来我们分别介绍这里发生的事情。

首先,包装函数的声明以 (function... 而不仅是以 function... 开始。尽管看上去这并不 是一个很显眼的细节,但实际上却是非常重要的区别。函数会被当作函数表达式而不是一 个标准的函数声明来处理。 区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位 置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处。 比较一下前面两个代码片段。第一个片段中 foo 被绑定在所在作用域中,可以直接通过 foo() 来调用它。第二个片段中 foo 被绑定在函数表达式自身的函数中而不是所在作用域中。 换句话说,(function foo(){ .. }) 作为函数表达式意味着 foo 只能在 .. 所代表的位置中 被访问,外部作用域则不行。foo 变量名被隐藏在自身中意味着不会非必要地污染外部作 用域。

以下部分原文出处: http://www.cnblogs.com/isaboy/p/javascript_function.html

Javascript Function无处不在,而且功能强大!通过Javascript函数可以让JS具有面向对象的一些特征,实现封装、继承等,也可以让代码得到复用。但事物都有两面性,Javascript函数有的时候也比较“任性”,你如果不了解它的“性情”,它很可能给你制造出一些意想不到的麻烦(bugs)出来。

  Javascript Function有两种类型:

  1)函数声明(Function Declaration);

    // 函数声明
function funDeclaration(type){
return type==="Declaration";
}

  2)函数表达式(Function Expression)。

    // 函数表达式
var funExpression = function(type){
return type==="Expression";
}

  上面的代码看起来很类似,感觉也没什么太大差别。但实际上,Javascript函数上的一个“陷阱”就体现在Javascript两种类型的函数定义上。下面看两段代码(分别标记为代码1段和代码2段):

1     funDeclaration("Declaration");//=> true
2 function funDeclaration(type){
3 return type==="Declaration";
4 }
1     funExpression("Expression");//=>error
2 var funExpression = function(type){
3 return type==="Expression";
4 }

  用函数声明创建的函数funDeclaration可以在funDeclaration定义之前就进行调用;而用函数表达式创建的funExpression函数不能在funExpression被赋值之前进行调用。
为什么会这样呢?!这就要理解Javascript Function两种类型的区别:用函数声明创建的函数可以在函数解析后调用(解析时进行等逻辑处理);而用函数表达式创建的函数是在运行时进行赋值,且要等到表达式赋值完成后才能调用。
这个区别看似微小,但在某些情况下确实是一个难以发现的陷阱。出现这个陷阱的本质原因体现在这两种类型在Javascript function hoisting(函数提升)和运行时机(解析时/运行时)上的差异。关于变量提升,可参见我的另外一篇博文http://www.cnblogs.com/isaboy/p/javascript_hoisting.html。上面两段代码的函数提升可示意为下图:

代码1段JS函数等同于:

    function funDeclaration(type){
return type==="Declaration";
}
funDeclaration("Declaration");//=> true

代码2段JS函数等同于:

    var funExpression;
funExpression("Expression");//==>error
funExpression = function(type){
return type==="Expression";
}

  上述代码在运行时,只定义了funExpression变量,但值为undefined。因此不能在undefined上进行函数调用。此时funExpression赋值语句还没执行到。为了进一步加深JS函数两种类型的区别,下面给出一个更具迷惑性的示例,请看下面的代码(代码段4):

 1     var sayHello;
2 console.log(typeof (sayHey));//=>function
3 console.log(typeof (sayHo));//=>undefined
4 if (true) {
5 function sayHey() {
6 console.log("sayHey");
7 }
8 sayHello = function sayHo() {
9 console.log("sayHello");
10 }
11 } else {
12 function sayHey() {
13 console.log("sayHey2");
14 }
15 sayHello = function sayHo() {
16 console.log("sayHello2");
17 }
18 }
19 sayHey();// => sayHey2
20 sayHello();// => sayHello

  分析:sayHey是用函数声明创建的,在JS解析时JS编译器将函数定义进行了函数提升,也就是说,在解析JS代码的时候,JS编译器(条件判断不形成新的作用域,两个sayHey函数定义都被提升到条件判断之外)检测到作用域内有两个同名的sayHey定义,第一个定义先被提升,第二个定义接着被提升(第二个定义在第一个定义之下),第二个定义覆盖了第一个sayHey定义,所以sayHey()输出sayHey2;而sayHello是用函数表达式创建的,其表达式的内容是在JS运行时(不是解析时)才能确定(这里条件判断就起到作用了),所以sayHello表达式执行了第一个函数定义并赋值,则sayHello()输出sayHello。

  代码段4的代码实际上等同于下面的代码(代码段5):

 1     var sayHello;
2 function sayHey() {
3 console.log("sayHey");
4 }
5 function sayHey() {
6 console.log("sayHey2");
7 }
8 console.log(typeof (sayHey));//=>function
9 console.log(typeof (sayHo));//=>undefined
10 if (true) {
11 //hoisting...
12 sayHello = function sayHo() {
13 console.log("sayHello");
14 }
15 } else {
16 //hoisting...
17 sayHello = function sayHo() {
18 console.log("sayHello2");
19 }
20 }
21 sayHey();// => sayHey2
22 sayHello();// => sayHello

  有的人也许会怀疑函数sayHey的定义是第二个覆盖第一个了么?我们可以把sayHey的源代码进行输出,有图有真相,如下图所示:

  总结

  Javascript 中函数声明和函数表达式是存在区别的,函数声明在JS解析时进行函数提升,因此在同一个作用域内,不管函数声明在哪里定义,该函数都可以进行调用。而函数表达式的值是在JS运行时确定,并且在表达式赋值完成后,该函数才能调用。这个微小的区别,可能会导致JS代码出现意想不到的bug,让你陷入莫名的陷阱中。

  最后附上代码段4中sayHello和sayHey两个函数的核心步骤(个人理解,若有异议欢迎留言探讨):

  上图为sayHello函数执行的主要步骤示意图。

js函数表达式和函数声明的区别的更多相关文章

  1. JS中函数表达式与函数声明的区别

    hello,沐晴又来更新啦,今天呢,跟大家讲讲让人头疼的函数表达式和函数声明,反正我当初看那本高级程序的时候,是没怎么看太透,哈哈.我是个比较重基础的人,跟我一起探讨函数表达式和函数声明的世界吧. 首 ...

  2. js基础:函数表达式和函数声明

    函数表达式和函数声明的区别.实际上,解析器在向执行环境中加载数据是,对函数表达式和函数声明并非一视同仁.解析器会率先读取函数声明,并使其在执行任何代码之前可用.而函数表达式,则必须等到解析器执行到它所 ...

  3. js中 函数声明/函数表达式/匿名函数/箭头函数/立即执行函数

    函数声明: function add(a, b) { // ... } 1.顾名思义,声明一个函数, 用关键字 “function” 来告诉,这是一个函数. 2.任何地方,想用就可以拿过来使用 函数表 ...

  4. 第七章 函数表达式和函数声明,关于this对象 ,私有作用域(function(){})() ,私有变量和特权方法

    一:函数表达式和函数声明 1:函数声明和函数表达式的区别 ①函数声明不需要分号结尾 ②函数声明有函数提升的特点 ③函数声明后面不能跟圆括号直接调用,因为javascript将function关键字当作 ...

  5. 变量声明置顶规则、函数声明及函数表达式和函数的arguments属性初始化

    一.变量声明和变量赋值: if (!("a" in window)) { ; } alert(a);//a为? 你可能认为alert出来的结果是1,然后实际结果是“undefine ...

  6. JavaScript函数表达式与函数声明

    什么是函数? 函数是事件驱动或者被调用时执行的重复代码块. 作用域: 1. 全局作用域 2. 函数作用域(局部作用域) var i = 100; //全局作用域 function fun(){ var ...

  7. javascript笔记05:函数表达式和函数语句的区别

    1.首先是函数语句: myfunc(); function myfunc() { //执行一些语句 } 当函数语句被定义的时候,在一个脚本代码被优先考虑,因此,无论该函数是定义之前或者定义之后都可以被 ...

  8. Js中函数声明和函数表达式的区别

    先看以下几段烧脑的代码: f();//=>? var f = function () { console.log("var"); } function f() { conso ...

  9. js 函数声明和函数表达式的区别

    javascript中声明函数的方法有两种:函数声明和函数表达式. 区别如下: 1).以函数声明的方法定义的函数,函数名是必须的,而函数表达式的函数名是可选的. 2).以函数声明的方法定义的函数,函数 ...

随机推荐

  1. EasyUI+Knockout实现经典表单的查看、编辑

    此文章是基于 1. 搭建SpringMVC+Spring+Hibernate平台 2. 自制xml实现SQL动态参数配置 3. 利用DetachedCriteria构建HQL参数动态匹配 4. 常用日 ...

  2. JavaScript学习笔记5 之 计时器 & scroll、offset、client系列属性 & 图片无缝滚动

    一.计时器 setInterval ( 函数/名称 , 毫秒数 )表示每经过一定的毫秒后,执行一次相应的函数(重复) setTimeout ( 函数/名称 , 毫秒数 ) 表示经过一定的毫秒后,只执行 ...

  3. 获得设备的唯一标识符UDID

    在IOS5之后,苹果为避免根据UDID获得用户的信息,而禁止使用uniqueIdentifier获得UDID,但是仍有些应用需要根据UDID区分设备 有一个系统的库IOKit.framework可以获 ...

  4. 拖拽手势和清扫手势冲突时(UIPanGestureRecognizer和UISwipeGestureRecognizer冲突时)

    故事发生在这样的情境上:给整个控制器添加了一个拖拽手势,然后又在控制上的每个Cell上加了左滑清扫手势,然后问题来了:只有拖拽手势起作用,而左滑手势没有效果了,然后怎么解决这个问题呢!先上图: 当给整 ...

  5. ArrayList、Vector、HashMap、HashSet的默认初始容量、加载因子、扩容增量

    当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使效率大大降低. 加载因 ...

  6. [Modern OpenGL系列(一)]十步搞定OpenGL开发环境

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51292143 OpenGL官网:https://www.opengl.or ...

  7. 使用c/c++扩展python

    用python脚本写应用比较方便,但有时候由于种种原因需要扩展python(比如给程序提供python接口等). 之前一直想整理下,今天终于坐下来把这件事情给做了,这里记录下,也方便我以后查阅. 说明 ...

  8. VMware中CPU分配不合理以及License限制引起的SQL Scheduler不能用于查询处理

    有一台SQL Server(SQL Server 2014 标准版)服务器中的scheduler_count与cpu_count不一致,如下截图所示: SELECT  cpu_count ,      ...

  9. Hadoop技巧系列索引

    本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink Hadoop技巧(01):插件,终端权限 Had ...

  10. tomcat linux下的部署安装

    Tomcat在Linux上的安装与配置 转自http://blog.csdn.net/gyming/article/details/36060843     以下使用的Linux版本为: Redhat ...