Serenader

Learning by sharing

JavaScript 中的作用域和声明提升

作用域

JavaScript 中没有块级作用域,只存在函数作用域,函数是它的第一类。具体表现如下:
for(var i = 0; i < 10; i++){
	//do something
 }
 
alert(i);//10

var example = function(){
	var x = 10;
    //do something
}

alert(x);//ReferenceError: x is not defined
上面的例子应该可以清楚地解释了 JavaScript 的作用域问题。在 for 里面定义的变量其实都是全局变量。因为 JavaScript 没有块级作用域。然后,在函数内部定义的变量均为局部变量。当在函数内部引用某个变量时,解释器会先在函数内部的作用域中查找该变量。假如在函数内部的作用域中找到了该变量,则返回该变量的值。如果在函数内部的作用域中找不到该变量,则在外一层作用域中查找变量。如下面的例子:
var i = 10;
var x = 11;
var y = 20;
var ex = function(){
      var i = 1;
      var x = 2;
      alert(i); //1
      var a = function(){
          alert(x); //2
          alert(y); //20
      }
      a();
}
ex();

声明提升

在 JavaScript 中定义变量和函数时,其实会经历一个预编译过程。具体表现为:函数声明和变量声明总是被 JavaScript 解释器隐式地提升到当前作用域的顶端。来看例子:
x(); //hi
function x(){
	alert("hi");
}


hoist();
var hoist = function(){
	alert(a); //undefined
    alert(b);
    var a = 10;
}
hoist();
上面的例子其实包含了几个方面的内容。我们先来看第一个例子。这个例子中,我们在函数声明之前就执行了该函数,结果居然没报错。而在下面的例子中,我们也是在定义了 hoist 函数之前就执行它,结果控制台报错: hoist is not a function ,而最后面再执行该函数又能正常响应。这是为什么呢?
在 JavaScript 中,通过函数声明定义的函数,都会被解析器提升到作用域的顶端。所以在第一个例子中,实际的顺序是这样的:
function x(){
	alert("hi");
}
x();
因此该函数能够正常执行。而对于通过函数表达式定义的函数则没有这个功能。第二个例子就能看出。在函数表达式前执行该函数会报错,提示该变量不是函数。
第二个函数里面涉及到了变量声明提升的问题。你可以在浏览器中看到,该函数执行之后 alert 返回的是 undefined 之后浏览器就报错了。为什么呢?和函数声明一样,使用变量声明会使得变量的声明被隐式地提升到作用域的顶端。但是这个提升仅仅只是声明,而赋值部分则没有。所以第二个函数的实际顺序是这样子的:
var hoist;
...
hoist = function(){
	var a;
    alert(a);
    alert(b);
    a = 10;
}
所以对 a 执行 alert 只是返回 undefined ,而不是跟后面那个没有定义的 b 一样,浏览器报错,显示 b is not defined 。
总的来说,就是,通过函数声明(通过 function 声明的函数)和变量声明(所有通过 var 声明的变量)会使得该变量或函数被隐式地提升到该作用域的顶端。但是只有函数声明有这种效果,函数表达式没有。变量声明中也只提升声明部分,而赋值部分也没有提升。
另外需要注意的是,如果在同一个作用域下定义了两个同名的函数和变量,则函数声明比变量声明拥有更高的优先级。
完。