函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部

请注意,变量赋值并没有被提升,只是声明被提升了。

函数的声明比变量的声明具有高的优先级

下面的程序是什么结果?

  1. var foo = 1;
  2. function bar() {
  3. if (!foo) {
  4. var foo = 10;
  5. }
  6. alert(foo);
  7. }
  8. bar();

结果是10;

那么下面这个呢?

  1. var a = 1;
  2. function b() {
  3. a = 10;
  4. return;
  5. function a() {}
  6. }
  7. b();
  8. alert(a);

结果是1,因为函数的提升要比变量的提升有更高的优先级,所以实际上等价于

  1. var a = 1;
  2. function b() {
  3. function a() {}
  4. a = 10;    //此处将函数名a指向10,而var a = 1不会受影响
  5. return;
  6. }
  7. b();
  8. alert(a);

吓你一跳吧?发生了什么事情?这可能是陌生的,危险的,迷惑的,同样事实上也是非常有用和印象深刻的javascript语言特性。对于这种表现行为,我不知道有没有一个标准的称呼,但是我喜欢这个术语:“Hoisting (变量提升)”。这篇文章将对这种机制做一个抛砖引玉式的讲解,但是,首先让我们对javascript的作用域有一些必要的理解。

Javascript的作用域

对于Javascript初学者来说,一个最迷惑的地方就是作用域;事实上,不光是初学者。我就见过一些有经验的javascript程序员,但他们对scope理解不深。javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言,像下面的C程序:

  1. #include <stdio.h>
  2. int main() {
  3. int x = 1;
  4. printf("%d, ", x); // 1
  5. if (1) {
  6. int x = 2;
  7. printf("%d, ", x); // 2
  8. }
  9. printf("%d\n", x); // 1
  10. }

输出结果是1 2 1,这是因为C家族的语言有块作用域,当程序控制走进一个块,比如if块,只作用于该块的变量可以被声明,而不会影响块外面的作用域。但是在Javascript里面,这样不行。看看下面的代码:

  1. var x = 1;
  2. console.log(x); // 1
  3. if (true) {
  4. var x = 2;
  5. console.log(x); // 2
  6. }
  7. console.log(x); // 2

结果会是1 2 2。因为javascript是函数作用域。这是和c家族语言最大的不同。该程序里面的if并不会创建新的作用域。

对于很多C,c++,java程序员来说,这不是他们期望和欢迎的。幸运的是,基于javascript函数的灵活性,这里有可变通的地方。如果你必须创建临时的作用域,可以像下面这样:

  1. function foo() {
  2. var x = 1;
  3. if (x) {
  4. (function () {
  5. var x = 2;
  6. // some other code
  7. }());
  8. }
  9. // x is still 1.
  10. }

这种方法很灵活,可以用在任何你想创建临时的作用域的地方。不光是块内。但是,我强烈推荐你花点时间理解javascript的作用域。它很有用,是我最喜欢的javascript特性之一。如果你理解了作用域,那么变量提升就对你显得更有意义。

变量声明,命名,和提升

变量声明

Js编译器会把变量声明看成两个部分分别是声明操作(var a)和赋值操作(a=2)

声明操作在编译阶段进行,声明操作会被提升到执行环境的顶部,值是undefined(表示未初始化)

赋值操作会被留在原地等待执行阶段

 1 var a = 2;
2
3 function foo() {
4 console.log(a); //undefined
5 var a = 10;
6 console.log(a); //10
7 }
8
9 foo();
10
11 // 相当于
12
13 var a = 2;
14
15 function foo() {
16 var a;
17 console.log(a); //undefined
18 a = 10;
19 console.log(a); //10
20 }
21
22 foo();

函数声明

定义函数有两种方式:函数声明和函数表达式

函数声明提升会在编译阶段把声明和函数体整体都提前到执行环境顶部,所以我们可以在函数声明之前调用这个函数

函数表达式,其实就是变量声明的一种,声明操作会被提升到执行环境顶部,并赋值undefined。赋值操作被留在原地等到执行。

1 // 函数声明
2
3 foo(); //100
4
5 function foo(){
6 console.log(100);
7 }
 1 // 函数表达式
2 baz(); // TypeError: baz is not a function
3
4 var baz = function(){
5 console.log(200);
6 }
7
8 //相当于
9
10 var baz;
11
12 baz();
13
14 baz = function() {
15 console.log(200);
16 };

在javascript,变量有4种基本方式进入作用域:

  • 1 语言内置:所有的作用域里都有this和arguments;(译者注:经过测试arguments在全局作用域是不可见的)
  • 2 形式参数:函数的形式参数会作为函数体作用域的一部分;
  • 3 函数声明:像这种形式:function foo(){};
  • 4 变量声明:像这样:var foo;

函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部。这个意思是,像下面的代码:

  1. function foo() {
  2. bar();
  3. var x = 1;
  4. }

实际上会被解释成:

  1. function foo() {
  2. var x;
  3. bar();
  4. x = 1;
  5. }

无论定义该变量的块是否能被执行。下面的两个函数实际上是一回事:

  1. function foo() {
  2. if (false) {
  3. var x = 1;
  4. }
  5. return;
  6. var y = 1;
  7. }
  8. function foo() {
  9. var x, y;
  10. if (false) {
  11. x = 1;
  12. }
  13. return;
  14. y = 1;
  15. }

请注意,变量赋值并没有被提升,只是声明被提升了。但是,函数的声明有点不一样,函数体也会一同被提升。但是请注意,函数的声明有两种方式:

  1. function test() {
  2. foo(); // TypeError "foo is not a function"
  3. bar(); // "this will run!"
  4. var foo = function () { // 变量指向函数表达式
  5. alert("this won't run!");
  6. }
  7. function bar() { // 函数声明 函数名为bar
  8. alert("this will run!");
  9. }
  10. }
  11. test();

这个例子里面,只有函数式的声明才会连同函数体一起被提升。foo的声明会被提升,但是它指向的函数体只会在执行的时候才被赋值。

上面的东西涵盖了提升的一些基本知识,它们看起来也没有那么迷惑。但是,在一些特殊场景,还是有一定的复杂度的。

变量解析顺序

最需要牢记在心的是变量解析顺序。记得我前面给出的命名进入作用域的4种方式吗?变量解析的顺序就是我列出来的顺序。一般来说,如果一个名称已经被定义,则不会被其他相同名称的属性覆盖。这是说,(译者没理解这句,所以先做删除样式) 函数的声明比变量的声明具有高的优先级这并不是说给那个变量赋值不管用,而是声明不会被忽略了。 (译者注: 关于函数的声明比变量的声明具有高的优先级,下面的程序能帮助你理解)

  1. <script>
  2. function a(){
  3. }
  4. var a;
  5. alert(a);//打印出a的函数体
  6. </script>
  7. <script>
  8. var a;
  9. function a(){
  10. }
  11. alert(a);//打印出a的函数体
  12. </script>
  13. //但是要注意区分和下面两个写法的区别:
  14. <script>
  15. var a=1;
  16. function a(){
  17. }
  18. alert(a);//打印出1
  19. </script>
  20. <script>
  21. function a(){
  22. }
  23. var a=1;
  24. alert(a);//打印出1
  25. </script>

这里有3个例外:
1 内置的名称arguments表现得很奇怪,他看起来应该是声明在函数形式参数之后,但是却在函数声明之前。这是说,如果形参里面有arguments,它会比内置的那个有优先级。这是很不好的特性,所以要杜绝在形参里面使用arguments;
2 在任何地方定义this变量都会出语法错误,这是个好特性;
3 如果多个形式参数拥有相同的名称,最后的那个具有优先级,即便实际运行的时候它的值是undefined;

命名函数

你可以给一个函数一个名字。如果这样的话,它就不是一个函数声明,同时,函数体定义里面的指定的函数名( 如果有的话,如下面的spam, 译者注)将不会被提升, 而是被忽略。这里一些代码帮助你理解:

    1. foo(); // TypeError "foo is not a function"
    2. bar(); // valid
    3. baz(); // TypeError "baz is not a function"
    4. spam(); // ReferenceError "spam is not defined"
    5. var foo = function () {}; // foo指向匿名函数
    6. function bar() {}; // 函数声明
    7. var baz = function spam() {}; // 命名函数,只有baz被提升,spam不会被提升。
    8. foo(); // valid
    9. bar(); // valid
    10. baz(); // valid
    11. spam(); // ReferenceError "spam is not defined"

js变量和函数声明的提升的更多相关文章

  1. js变量和函数声明的提升(转)

    原文:http://zha-zi.iteye.com/blog/2037026 下面的程序是什么结果? var foo = 1; function bar() { if (!foo) { var fo ...

  2. JavaScript中变量和函数声明的提升

    现象: 1.在JavaScript中变量和函数的声明会提升到最顶部执行. 2.函数的提升高于变量的提升. 3.函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找. 4.匿名函数不会提升. ...

  3. 前端面试题总结一(js变量和函数声明提前相关)

    好久没有更新博客了,^_^写写博客吧!下面是我总结的一些面试题,希望对大家有所帮助 (1)题目如下: alert(a)  var a=1  function a(){    alert(a) } 好多 ...

  4. Javascript 函数声明先提升还是变量先提升

    大家都知道js 分为词法阶段 和执行阶段 也知道它是因为var变量和函数声明会提升 但是你知道他们两个谁先提升的吗 测试一下 function test(){ alert(4); } var test ...

  5. js变量和函数提升的小结

    对于变量和函数一起的提升说法,我比较认同"LittleBear"的说法. 比如: <script> console.log(a)//function a(){} var ...

  6. JS中的提升(即变量和函数声明移动到代码顶部)

    先看代码(第一个代码片段): console.log(a); var a = 1; 如果你认为这是一段不合法的代码,在调用console.log()的时候会输出undefined,你完全正确.但是如果 ...

  7. js自执行函数、调用递归函数、圆括号运算符、函数声明的提升

    前言 起因是我要在jquery的ajax中需要根据返回值来决定是否继续发起ajax请求,这是一个有条件的循环,符合条件就跳出.可以使用while循环的,但是想了想还是递归调用好用. 调用递归函数 递归 ...

  8. js基础 js自执行函数、调用递归函数、圆括号运算符、函数声明的提升 js 布尔值 ASP.NET MVC中设置跨域

    js基础 目录 javascript基础 ESMAScript数据类型 DOM JS常用方法 回到顶部 javascript基础 常说的js包括三个部分:dom(文档document).bom(浏览器 ...

  9. js 变量、函数提升

    js 变量.函数提升 先简单理解下作用域的概念,方便对变量与函数提升的概念的理解 function foo() { var x = 1; if (x) { var x = 2; } console.l ...

随机推荐

  1. (转)一位资深程序员大牛给予Java初学者的学习路线建议

    Java学习这一部分其实也算是今天的重点,这一部分用来回答很多群里的朋友所问过的问题,那就是你是如何学习Java的,能不能给点建议?今天我是打算来点干货,因此咱们就不说一些学习方法和技巧了,直接来谈每 ...

  2. springboot配置Druid监控

    整体步骤: (1)    ——   Druid简单介绍,具体看官网: (2)     —— 在pom.xml配置druid依赖包: (3)    ——  配置application.propertie ...

  3. ActiveSync中的http内容组织

    1. POST Request 数据格式 Request-line Request-headers CR/LF Request Body Request-line POST <URI> H ...

  4. hexo+github page +markdown问题汇总

    1.没有权限提交 解决办法:把git版本由2.x改为1.9 未完待续

  5. 步步为营-94-GridView中的DropDownlist值得获取与绑定

    bug场景: 例如这种"计税方式"是下拉列表的,当选择"编辑"时候,数据会丢失 修改方式,前台对应修改 后台代码在databound时候给绑定值 测试效果

  6. 三 os模块

    os模块是与操作系统交互的一个接口 os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname") 改变当前脚本工作目录:相 ...

  7. 一 time与datetime模块

    时间戳(timestamp):通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量.我们运行“type(time.time())”,返回的是float类型. 格式化的时间字 ...

  8. 基于容器的ETCD集群脚本

    其实是从上一篇的脚本里剥离出来的. 加深一下印象吧. docker run \ -d \ -p ${ETCD_CLI_PORT}:${ETCD_CLI_PORT} \ -p ${ETCD_CLU_PO ...

  9. HL7体系入门级介绍【转】

    HL7的简单介绍1)HL7  缩写于Health Level Seven,是创建于1987年,用来发展独立卫生保健行业的电子交换交换标准,经过多年的发展,HL7已经有多个版本,     目前我们 的集 ...

  10. python之集合set

    1.测试 # python2和python3方法列表相同 ops23 = ['add', 'clear', 'copy', 'difference', 'difference_update', 'di ...