你的位置:首页 > Java教程

[Java教程]关于JS变量提升的一些坑


1 function log(str) {2  // 本篇文章所有的打印都将调用此方法3   console.log(str);4 }

 

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

变量声明、命名、提升


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

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

 

变量提升

 

function test1() {   a = 5;   log(a);    log(window.a);    var a = 10;   log(a);   }  test1();

依次会输出 5 、undefined 、10   因为在解析时候是等价于

1 var a;2 a=5;3 log(a);4 log(window.a);5 a=10;6 log(a);

 接着看另外一个例子:

 1 function test2() { 2  var a = 1; 3  var b = 2; 4  var c = 3; 5 } 6 /*test2中的语句,是这样被执行的 这个时候就把变量提升了 7   
8 function test2(){ 9 var a,b,c; 10 var a = 1;11 var b = 2;12 var c = 3; 13 }14 */

只有函数级作用域,if语句不会有:test3():

function test3(){
var a = 1;
log(a); // 1 
if (true) {
var a = 2;
log(a); //2 
}
log(a); // 2 
}

 

函数的提升


我们写JS的时候,通常会有两种写法:

  • 函数表达式 var fn=function fn(){}
  • 函数声明方式 function fn(){}

我们需要重点注意的是,只有函数声明形式才能被提升。
变量赋值并没有被提升,只是声明被提升了。但是,函数的声明有点不一样,函数体也会一同被提升

 1 function test3() { 2   fn(); 3  function fn() { 4   log("我来自fn"); 5   } 6  } 7  test3(); 8 function test4() { 9  fn(); // fn is not a function10  var fn = function fn() {11   alert("我来自 fn test4");12   }13  }14 test4();

 

 

函数表达式需要注意的

  • 在function内部,fn完全等于fn1
  • 在function外面,fn1则是 not defined
 1 function test5() { 2    var fn = function fn1() { 3     log(fn === fn1); // true 4     log(fn == fn1); // true 5    } 6    fn(); 7    log(fn === fn1); // fn1 is not defined 8    log(fn == fn1); // fn1 is not defined 9   }10   test5();

!兼容
// b();
// var a = function b() {alert('this is b')};
// 则ie下是可以执行b的. 说明不同浏览器在处理函数表达式细节上是有差别的.

 

补充一点函数表达式

定义里面的指定的函数名是不是被提升的

 1 function text7() { 2  a(); // TypeError "a is not a function"  3  b(); 4  c(); // TypeError "c is not a function"  5  d(); // ReferenceError "d is not defined" 6  7  var a = function() {};  // a指向匿名函数  8  function b() {};     // 函数声明  9  var c = function d() {}; // 命名函数,只有c被提升,d不会被提升。10 11  a();13  b();14  c();15  d(); // ReferenceError "d is not defined"16 }17 text7();

 

大家先看下面一段代码test6,思考一下会打印什么?

 1 function text6() { 2  var a = 1; 3  function b() { 4   a = 10; 5    return; 6    function a() {} 7   } 8   b(); 9   log(a);     // ?10 }11 text6();

||

||

||

|| 输出在下面

||

||

||

||

||

||

what? 什么鬼?为什么是1?
这里需要注意的是,在function b()中,
var = a // function 类型的
a=10; // 重新把10复制给a,  此时的a是function b()中的内部变量
return;
function a() {} // 不会被执行

所以,外面输出的a 依旧是最开始定义的全局变量

 

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

 1 function text6() { 2  function a() {} 3  var a; 4  log(a);        //打印出a的函数体 5  6  var b; 7  function b() {} 8  log(b);         //打印出b的函数体  9 10  // !注意看,一旦变量被赋值后,将会输出变量11  var c = 1212  function c() {}13  log(c);         //1214  15  function d() {}16  var d = 1217  log(d);        //1218 }19 text6();

 

变量解析的顺序


一般情况下,会按照最开始说的四种方式依次解析

  • 语言内置:
  • 形式参数: 
  • 函数声明: 
  • 变量声明: 

 也有例外:

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

 

CAO!这么多坑,以后肿么写代码?


用var定义变量。对于一个名称,在一个作用域里面永远只有一次var声明。这样就不会遇到作用域和变量提升问题。

ECMAScript参考文档关于作用域和变量提升的部分:
    如果变量在函数体类声明,则它是函数作用域。否则,它是全局作用域(作为global的属性)。变量将会在执行进入作用域的时候被创建。块(比如if(){})不会定义新的作用域,只有函数声明和全局性质的代码(单个JS文件)才会创造新的作用域。变量在创建的时候会被初始化为undefined。如果变量声明语句里面带有赋值操作,则赋值操作只有被执行到的时候才会发生,而不是创建的时候。

 

最后,

由于时间仓促,demo有很多不足之处,多谅解。