javascript变量作用域和变量提升(hoisting)

作用域(Scoping)

        作用域我的理解就是:javascript中变量或函数产生作用、而不会对外产生影响的封闭空间。外部不可以访问内部变量或函数,但内部能够访问外部。

ES5

       JS只有俩种作用域:全局作用域和函数作用域

  1. 全局作用域:所有地方都可以访问
  2. 函数作用域:只能在函数内部访问

ES6:

       JS增加了块级作用域(最近大括号的作用范围),但仅限于let声明的变量

例1

// 全局变量
var i = 0 ;
// 定义外部函数
function outer(){
    // 访问全局变量
    console.log(i); // 0
    function inner1(){
        console.log(i); // 0
    }

    function inner2(){
        console.log(i); // undefined
        var i = 1;
        console.log(i); // 1
    }
    inner1();
    inner2();
    console.log(i); // 0
}

第一个i:outer函数内未声明和定义i, js解析器会继续往函数外部找此变量,全局变量中找到i,所以结果为0;

第二个i:原理与第一个也一样;

总结1:js解析器查找变量,依次从作用域内部一层一层往外查找,找到后结束,找到最外部都没有找到,结果就为undefined(已声明,但未定义);

第三个i:结果是undefined,为什么?为什么?为什么?

             常见错误结果:0    inner2中变量i的声明与定义在此命令之后,找不到i变量,往函数外部查找,所以为全部变量i=0

             常见错误结果:1    inner2中变量i已经声明定义,所以函数内部已经找到i了,所以为1;

总结2:js中存在变量提升(hoisting)

变量提升(hoisting)

/**
 * 演示:变量temp被提升了,但是定义未被提升
 * @type {Date}
 */
var temp = new Date();
function f(){
    console.log(temp);
    if (false){
        var temp = "hello";
    }
}
f();

var hoisting

Because variable declarations (and declarations in general) are processed before any code is executed, declaring a variable anywhere in the code is equivalent to declaring it at the top. This also means that a variable can appear to be used before it's declared. This behavior is called "hoisting", as it appears that the variable declaration is moved to the top of the function or global code.

这段话翻译下来就是

因为变量申明是在任意代码执行前处理的,在代码区中任意地方申明变量和在最开始(最上面)的地方申明是一样的。也就是说,看起来一个变量可以在申明之前被使用!这种行为就是所谓的“hoisting”,也就是变量提升,看起来就像变量的申明被自动移动到了函数或全局代码的最顶上。

注意:仅仅是申明提升了,定义并不会被提升。

如此,上面这段代码其实就是下面的形式:

javascript变量作用域和变量提升(hoisting)_第1张图片

所以,这样就应该理解了,console输出的时候,tmp变量仅仅是申明了但未定义,所以输出应该是undefined。

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

javascript变量作用域和变量提升(hoisting)_第2张图片
这里a被提升,但因为定义在后,所以输出undefined

这里a虽然被提升,但却报了引用错误!

之所以或这样

因为这样的原因,推荐的做法是在申明变量的时候,将所用的变量都写在作用域(全局作用域或函数作用域)的最顶上,这样代码看起来就会更清晰,更容易看出来那个变量是来自函数作用域的,哪个又是来自作用域链(本文不对此多做解释,请读者自行百度,有机会再补充说明)。

重复声明

javascript变量作用域和变量提升(hoisting)_第3张图片

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

javascript变量作用域和变量提升(hoisting)_第4张图片

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

如果是函数和变量类型同时申明定义了,会发生什么事情呢?看下面的代码


A

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

而如果是这样的形式呢


B

它的输出却变成:undefined

为什么会这样呢?

原来函数提升分为两种情况:

      一种:函数申明。就是上面A,function foo(){}这种形式

     另一种:函数表达式。就是上面B,var foo=function(){}这种形式

第二种形式其实就是var变量的声明定义,因此上面的B输出结果为undefined应该就能理解了。

而第一种函数申明的形式,在提升的时候,会被整个提升上去,包括函数定义的部分!因此A跟下面的这种方式是等价的!

原因是因为:1、函数声明被提升到最顶上;2、申明只进行一次,因此后面var foo='i am text'的申明会被忽略。

总结

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

      1、所有申明都会被提升到作用域的最顶上,变量声明提升仅仅是声明提升,定义并不提升;函数声明会连带 定义一起被提升

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

           如:var i = 0 ; var i = 1     相当于 var i ;  i = 0 ; i = 1;  

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


你可能感兴趣的:(javascript,javascript变量提升,作用域,hoisting,变量查找顺序,变量声明与定义)