首页 > 代码库 > js变量作用域--变量提升

js变量作用域--变量提升

1、JS作用域

在ES5中,js只有两种形式的作用域:全局作用域和函数作用域,在ES6中,新增了一个块级作用域(最近的大括号涵盖的范围),但是仅限于let方式申明的变量。

2、变量声明

1 var x;      //变量声明
2 var x=1;    //变量声明并赋值
3 x = 1;    // 定义全局变量并赋值

3、函数声明

function fn(){};     //函数声明并定义
var fn = function(){};    // 实际上是定义了一个局部变量fn和一个匿名函数,然后把这个匿名函数赋值给了fn

4、变量提升

var tmp = new Date();
function fn(){
    console.log(tmp);  //Wed Jul 12 2017 22:11:56 GMT+0800 (中国标准时间)
}
fn();

a情形

var tmp = new Date();
function fn(){
    console.log(tmp);    //undefined
    if(false){
        var tmp = ‘hello‘;
    }
}
fn();

b情形

var tmp = new Date();
function fn(){
    console.log(tmp);    //undefined
    if(true){
        var tmp = ‘hello‘;
    }
}
fn();

c情形

从上面可以看到,b情形和c情形为什么不同于a情形,就是因为变量提升了(ps: c情形不同于b情形的是判断条件为true,但是这里不是看代码有没有被执行,是看变量有没有被定义)。fn函数里面定义了同名变量tmp,无论在函数的任何位置定义tmp变量,它都将被提升到函数的最顶部。等同于下面情形:

var tmp = new Date();
console.log(tmp);
function fn(){
        var tmp;
    console.log(tmp);    //undefined
    if(false){
        var tmp = ‘hello‘;
    }
}
fn();

这里需要说明的是,虽然所有的申明(包括ES5的var、function,和ES6的function *、let、const、class)都会被提升,但是var、function、function *和let、const、class的的提升却并不相同!具体原因可以看这里的说明(大体的意思是虽然let,const,class也被提升了,但是却并不会被初始化,这时候去访问他们则会报ReferenceError异常,他们需要到语句执行的时候才会被初始化,而在被初始化之前的状态叫做temporal dead zone)。

因为这样的原因,推荐的做法是在申明变量的时候,将所用的变量都写在作用域(全局作用域或函数作用域)的最顶上,这样代码看起来就会更清晰,更容易看出来那个变量是来自函数作用域的,哪个又是来自作用域链。

5、重复声明

var x = 1;
console.log(x);
if(true){
    var x = 2;
    console.log(x);
}
console.log(x);

上面的输出其实是:1 2 2。虽然看起来里面x申明了两次,但上面说了,js的var变量只有全局作用域和函数作用域两种,且申明会被提升,因此实际上x只会在最顶上开始的地方申明一次,var x=2的申明会被忽略,仅用于赋值。也就是说上面的代码实际上跟下面是一致的:

var x = 1;
console.log(x);
if(true){
    x = 2;
    console.log(x);
}
console.log(x);

6、函数和变量同时提升的问题

console.log(fn);
function fn(){};
var fn = ‘string‘;

上面的输出结果其实是: function fn(){} ,也就是函数内容。

console.log(fn);
var fn = function fn(){};
var fn = ‘string‘;

这时输出结果就是undefined,知道上面的声明提升的道理就不难理解了。

总结:

要彻底理解JS的作用域和Hoisting,只要记住以下三点即可:

      1、所有申明都会被提升到作用域的最顶上

      2、同一个变量申明只进行一次,并且因此其他申明都会被忽略

      3、函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升

注意:

通过with语句,可以临时改变运行期上下文的作用域链,此时的对非var定义的变量进行访问,会首先访问with中对象的属性,然后才会向上顺着作用域链向上检查该属性。

js变量作用域--变量提升