Javascript函数范围和提升

时间:2011-09-21 21:23:50

标签: javascript scope scoping hoisting

我刚刚阅读了一篇关于JavaScript Scoping and Hoisting by Ben Cherry的精彩文章,其中提供了以下示例:

var a = 1;

function b() {
    a = 10;
    return;

    function a() {}
}
b();
alert(a);

使用上面的代码,浏览器会提示“1”。

我仍然不确定为什么它会返回“1”。他说的一些事情就像是:    所有函数声明都被提升到顶部。您可以使用函数来调整变量的范围。仍然没有为我点击。

18 个答案:

答案 0 :(得分:111)

功能提升意味着功能被移动到其范围的顶部。也就是说,

function b() {  
   a = 10;  
   return;  
   function a() {} 
} 

将由互操作者重写为

function b() {
  function a() {}
  a = 10;
  return;
}

很奇怪,是吗?

此外,在这个例子中,

function a() {}

表现与

相同
var a = function () {};

所以,从本质上讲,这就是代码正在做的事情:

var a = 1;                 //defines "a" in global scope
function b() {  
   var a = function () {}; //defines "a" in local scope 
   a = 10;                 //overwrites local variable "a"
   return;      
}       
b();       
alert(a);                 //alerts global variable "a"

答案 1 :(得分:6)

你必须记住的是它解析整个函数并在执行之前解析所有变量声明。所以....

function a() {} 

真的变成了

var a = function () {}

var a强制它进入局部范围,变量范围是通过整个函数,因此全局变量仍为1,因为您已通过将其作为函数声明为本地范围。

答案 2 :(得分:5)

函数a内挂起函数b

var a = 1; 
function b() { 
   function a() {} 
   a = 10; 
   return;
} 
b(); 
alert(a);

几乎就像使用var

var a = 1; 
function b() { 
   var a = function () {};
   a = 10; 
   return;
} 
b(); 
alert(a);

该函数在本地声明,设置a仅发生在本地范围内,而不是全局变量。

答案 3 :(得分:3)

  1. 函数声明function a(){}首先被提升,其行为类似于var a = function () {};,因此创建了本地范围a
  2. 如果您有两个具有相同名称的变量(一个在本地另一个变量中),则局部变量始终优先于全局变量。
  3. 设置a=10时,您设置的是本地变量a,而不是全局变量。
  4. 因此,全局变量的值保持不变,你得到,警告1

答案 4 :(得分:1)

function a() { }是一个函数语句,它在a函数的本地创建b变量。
解析函数时会创建变量,无论var或函数语句是否被执行。

a = 10设置此局部变量。

答案 5 :(得分:1)

这段小代码中争论的焦点是什么?

案例1:

function a(){}的正文中包含function b定义,如下所示。 logs value of a = 1

var a = 1;
function b() {
  a = 10;
  return;

  function a() {}
}
b();
console.log(a); // logs a = 1

案例2

排除function a(){}正文中的function b定义,如下所示。 logs value of a = 10

var a = 1;
function b() {
  a = 10;  // overwrites the value of global 'var a'
  return;
}
b();
console.log(a); // logs a = 10

观察将帮助您意识到语句console.log(a)记录以下值。

案例1: a = 1

案例2: a = 10

<强>假定

  1. var a已在全球范围内被词法定义和声明。
  2. a=10此语句将值重新分配给10,它在词法上位于函数b中。
  3. 两种情况的说明

    由于function definition with name property a与variable a相同。 variable a内的function body b成为局部变量。前一行表示a的全局值保持不变,a的本地值更新为10.

    所以,我们打算说的是下面的代码

    var a = 1;
    function b() {
      a = 10;
      return;
    
      function a() {}
    }
    b();
    console.log(a); // logs a = 1
    

    JS解释器解释如下。

    var a = 1;
    function b() {
      function a() {}
      a = 10;
      return;
    
    
    }
    b();
    console.log(a); // logs a = 1
    

    但是,当我们删除function a(){} definition,在函数b之外声明和定义的value of 'a'时,该值将被覆盖,并且在案例2中它将更改为10.该值将被覆盖,因为{{1引用全局声明,如果要在本地声明,我们必须写成a=10

    var a = 10;

    我们可以通过将var a = 1; function b() { var a = 10; // here var a is declared and defined locally because it uses a var keyword. return; } b(); console.log(a); // logs a = 1 中的name property更改为function a(){} definition以外的其他名称来进一步澄清我们的疑问

    'a'

答案 6 :(得分:1)

scpope&amp;关闭&amp;吊装(变更/功能)

  
      
  1. scpope:全局var可以在任何地方访问(整个文件   scope),本地var只能由本地访问   范围(功能/块范围)!
      注意:如果局部变量不使用   函数中的var关键字,它将成为一个全局变量!
  2.   
  3. closure:一个函数内部的另一个函数,可以访问   本地范围(父母职能)&amp;全球范围,遏制它的变种   其他人无法访问!除非,你将它作为返回值返回!
  4.   
  5. hoisting:将所有declare / undeclare vars / function移动到范围   顶部,而不是分配值或null!
      注意:它只是移动声明,而不是移动值!
  6.   

&#13;
&#13;
var a = 1;                
//"a" is global scope
function b() {  
   var a = function () {}; 
   //"a" is local scope 
   var x = 12; 
   //"x" is local scope 
   a = 10;
   //global variable "a" was overwrited by the local variable "a"  
   console.log("local a =" + a);
   return console.log("local x = " + x);
}       
b();
// local a =10
// local x = 12
console.log("global a = " + a);
// global a = 1
console.log("can't access local x = \n");
// can't access local x = 
console.log(x);
// ReferenceError: x is not defined
&#13;
&#13;
&#13;

答案 7 :(得分:1)

吊装是一个让我们更容易理解的概念。实际发生的是声明首先在其范围内完成,并且在此之后将发生分配(而不是在同一时间)。

当声明发生时,var a,然后function bb范围内,function a被声明。

此函数a将遮蔽来自全局范围的变量a。

声明完成后,值分配将开始,全局a将获得值1,内部function b将获得10。 当你执行alert(a)时,它将调用实际的全局范围变量。 对代码的这一小改动将使其更加清晰

        var a = 1;

    function b() {
        a = 10;
        return a;

        function a() { }
    }

    alert(b());
    alert(a);

答案 8 :(得分:0)

这是我对答案的回顾,其中包含更多注释和一个可以随意使用的小提琴。

// hoisting_example.js

// top of scope ie. global var a = 1
var a = 1;

// new scope due to js' functional (not block) level scope
function b() {
    a = 10; // if the function 'a' didn't exist in this scope, global a = 10
  return; // the return illustrates that function 'a' is hoisted to top
  function a(){}; // 'a' will be hoisted to top as var a = function(){};
}

// exec 'b' and you would expect to see a = 10 in subsequent alert
// but the interpreter acutally 'hoisted' the function 'a' within 'b' 
// and in doing so, created a new named variable 'a' 
// which is a function within b's scope
b();

// a will alert 1, see comment above
alert(a);

https://jsfiddle.net/adjavaherian/fffpxjx7/

答案 9 :(得分:0)

这是因为变量名称与函数名称相同意味着“a”。 因此,由于Javascript提升它尝试解决命名冲突,它将返回a = 1。

在我阅读“JavaScript Hoisting”http://www.ufthelp.com/2014/11/JavaScript-Hoisting.html

这篇文章之前,我对此感到困惑

希望它有所帮助。

答案 10 :(得分:0)

长篇大论!

但它会清除空气!

Java Script的工作方式是它涉及两个步骤:

  1. 编译(可以这么说) - 此步骤记录变量和函数声明及其各自的范围。它不涉及评估函数表达式:var a = function(){}或变量表达式(例如在3的情况下将x分配给var x =3;,这只是对RHS部分的评估。)< / p>

  2. 口译员:这是执行/评估部分。

  3. 检查以下代码的输出以获得理解:

    &#13;
    &#13;
    //b() can be called here!
    //c() cannot be called.
    console.log("a is " + a);
    console.log("b is " + b);
    console.log("c is " + c);
    var a = 1;
    console.log("Now, a is " + a);
    var c = function() {};
    console.log("Now c is " + c);
    
    function b() {
      //cannot write the below line:
      //console.log(e); 
      //since e is not declared.
      e = 10; //Java script interpreter after traversing from this function scope chain to global scope, is unable to find this variable and eventually initialises it with value 10 in global scope.
      console.log("e is " + e) //  works!
      console.log("f is " + f);
      var f = 7;
      console.log("Now f is " + f);
      console.log("d is " + d);
      return;
    
      function d() {}
    }
    b();
    console.log(a);
    &#13;
    &#13;
    &#13;

    让我们打破它:

    1. 在编译阶段, &#39;一个&#39;将在全球范围内注册,价值为undefined&#39;。 同样适用于&#39; c&#39;,此时它的价值将是&#39; undefined&#39;而不是&{39; function()&#39;。 &#39; b&#39;将被注册为全球范围内的功能。 在b的范围内,&#39; f&#39;将被注册为一个变量,此时此函数将不确定并且函数&#39; d&#39;将被注册。

    2. 当解释器运行时,可以在解释器到达实际表达式行之前访问声明的变量和function()(而不是表达式)。因此,变量将被打印出来{&#39; undefined&#39;并且可以在之前调用声明的匿名函数。但是,尝试在表达式初始化之前访问未声明的变量会导致如下错误:

    3. &#13;
      &#13;
      console.log(e)
      e = 3;
      &#13;
      &#13;
      &#13;

      现在,当您具有相同名称的变量和函数声明时会发生什么。

      答案是 - 函数始终在之前被提升,如果声明了相同的名称变量,则将其视为重复并忽略。记住,顺序无所谓。函数始终优先。但是在评估阶段,您可以将变量引用更改为任何内容(它存储最后一次分配的任何内容)请查看以下代码:

      &#13;
      &#13;
      var a = 1;
      console.log("a is " + a);
      
      function b() {
        console.log("a inside the function b is " + a); //interpreter finds                                'a' as function() in current scope. No need to go outside the scope to find 'a'.
        a = 3; //a changed
        console.log("Now a is " + a);
        return;
      
        function a() {}
      }
      var a; //treated as duplicate and ignored.
      b();
      console.log("a is still " + a + " in global scope"); //This is global scope a.
      &#13;
      &#13;
      &#13;

答案 11 :(得分:0)

Hoisting在JavaScript中意味着,在执行任何代码之前,在程序中执行变量声明。因此,在代码中的任何位置声明变量等同于在开头声明它。

答案 12 :(得分:0)

这完全取决于变量&#39; a&#39;的范围。让我通过将范围创建为图像来解释。

这里JavaScript将创建3个范围。

i)全球范围。 ii)函数b()范围。 iii)函数a()范围。

enter image description here

当你打电话给警报&#39;方法范围属于Global那个时间,所以它会选择变量&#39; a&#39;仅来自全球范围是1。

答案 13 :(得分:0)

吊装是JavaScript的行为概念。吊装(比如移动)是解释变量应该如何以及在何处被声明的概念。

在JavaScript中,变量可以在使用之后声明,因为函数声明和变量声明总是被JavaScript解释器无形地移动(“提升”)到其包含范围的顶部。

在大多数情况下,我们遇到两种类型的吊装。

1.可变声明吊装

让我们通过这段代码了解这一点。

 a = 5; // Assign 5 to a
 elem = document.getElementById("demo"); // Find an element 
 elem.innerHTML = a;                     // Display a in the element
 var a; // Declare a
  //output-> 5

此处变量a的声明将在编译时由javascript解释器无形地托管到顶部。所以我们能够获得a的价值。但是不建议使用这种变量声明方法,因为我们应该像这样将变量声明为top。

 var a = 5; // Assign and declare 5 to a
 elem = document.getElementById("demo"); // Find an element 
 elem.innerHTML = a;                     // Display a in the element
  // output -> 5

考虑另一个例子。

  function foo() {
     console.log(x)
     var x = 1;
 }

实际上是这样解释的:

  function foo() {
     var x;
     console.log(x)
     x = 1;
  }

在这种情况下,x将是未定义的

代码是否已执行包含变量声明并不重要。考虑这个例子。

  function foo() {
     if (false) {
         var a = 1;
     }
     return;
     var b = 1;
  }

这个功能就是这样。

  function foo() {
      var a, b;
      if (false) {
        a = 1;
     }
     return;
     b = 1;
  }

仅在变量声明中使用变量定义提升,而不是赋值。

  1. 功能声明吊装
  2. 与变量提升不同,功能体或指定值也将被提升。考虑一下这段代码

     function demo() {
         foo(); // this will give error because it is variable hoisting
         bar(); // "this will run!" as it is function hoisting
         var foo = function () {
             alert("this would not run!!");
         }
         function bar() { 
             alert("this will run!!");
         }
     }
     demo();
    

    现在我们理解变量和函数提升,现在让我们理解这段代码。

    var a = 1;
    function b() {
      a = 10;
      return;
       function a() {}
    }
    b();
    alert(a);
    

    这段代码将会是这样的。

    var a = 1;                 //defines "a" in global scope
     function b() {  
       var a = function () {}; //defines "a" in local scope 
        a = 10;                 //overwrites local variable "a"
        return;      
     }       
     b();       
     alert(a); 
    

    函数a()将在b()中包含局部作用域。 a()将被移动到顶部,同时用它的定义解释代码(仅在函数提升的情况下),因此现在将具有局部范围,因此不会影响在函数b()内具有其自己的范围的while的全局范围

答案 14 :(得分:0)

令人惊讶的是,这里没有一个答案提到范围链中执行上下文的相关性。

JavaScript引擎将当前正在执行的代码包装在执行上下文中。基本执行上下文是全局执行上下文。每次调用新函数时,都会创建一个新的执行上下文并将其放在执行堆栈中。想想坐在其他编程语言中的调用堆栈上的堆栈框架。后进先出。现在,每个执行上下文在JavaScript中都有自己的变量环境和外部环境。

我将使用以下示例进行演示。

1)首先,我们进入全局执行上下文的创建阶段。词汇环境的外部环境和可变环境都被创建。设置全局对象并将其放置在内存中,并使用特殊变量“ this”指向它。函数a及其代码以及具有未定义值的变量myVar放置在全局变量环境的内存中。请务必注意,函数a的代码未执行。它只是放在具有功能a的内存中。

2)其次,它是执行上下文的执行阶段。 myVar不再是未定义的值。它使用值1初始化,该值存储在全局变量环境中。调用功能a并创建一个新的执行上下文。

3)在功能a的执行上下文中,它经历其自己的执行上下文的创建和执行阶段。它有自己的外部环境和可变环境,因此也有自己的词汇环境。函数b和变量myVar存储在其变量环境中。此可变环境不同于全局可变环境。由于函数a在词法上(物理上在代码中)与全局执行上下文位于同一级别,因此其外部环境是全局执行上下文。因此,如果函数a引用不在其变量环境中的变量,则它将搜索作用域链,并尝试在全局执行上下文的变量环境中查找该变量。

4)函数b在函数a中调用。创建一个新的执行上下文。由于按词法位于函数a中,因此其外部环境为a。因此,当它引用myVar时,由于myVar不在函数b的变量环境中,因此它将在函数a的变量环境中查找。它在那里找到并在console.log中显示2。但是,如果变量不在函数a的变量环境中,则由于函数a的外部环境是全局执行上下文,因此作用域链将在此处继续搜索。

5)函数b和a完成执行后,将它们从执行堆栈中弹出。单线程JavaScript Engine在全局执行上下文中继续执行。它调用b函数。但是在全局变量环境中没有b函数,在全局执行上下文中没有其他外部环境可以搜索。因此,JavaScript引擎引发了异常。

function a(){
  function b(){
    console.log(myVar);
  }

  var myVar = 2;
  b();
}

var myVar = 1;
a();
b();
> 2
> Uncaught ReferenceError: b is not defined

以下示例显示了作用域链。在函数b的执行上下文的变量环境中,没有myVar。因此它搜索其外部环境,即功能a。函数a在其可变环境中也没有myVar。因此,引擎搜索功能a的外部环境,这是全局执行上下文的外部环境,并且在其中定义了myVar。因此,console.log将显示1。

function a(){
  function b(){
    console.log(myVar);
  }

  b();
}

var myVar = 1;
a();
> 1

关于执行上下文以及与之相关的词法环境(包括外部环境和变量环境),可以对JavaScript中的变量进行范围界定。即使您多次调用同一个函数,对于每次调用,它也会创建自己的执行上下文。因此,每个执行上下文在其变量环境中将具有自己的变量副本。没有共享变量。

答案 15 :(得分:0)

据我所知,变量声明和函数声明可以进行提升,例如:

a = 7;
var a;
console.log(a) 

JavaScript引擎内部发生了什么

var a;
a = 7;
console.log(a);
// 7

或者:

console.log(square(7)); // Output: 49
function square(n) { return n * n; }

它将变为:

function square(n) { return n * n; }
console.log(square(7)); // 49

但是不会挂起诸如变量赋值,函数表达式赋值之类的赋值: 例如:

console.log(x);
var x = 7; // undefined

可能会变成这样:

var x;
console.log(x); // undefined
x = 7;

答案 16 :(得分:0)

用一句话来描述用javascript托管是变量和函数被提升到声明它们的作用域的顶部。

enter image description here

我假设您是一个初学者,首先要理解正确的吊装,我们已经了解了 undefined ReferenceError

 var v;
 console.log(v);
 console.log(abc);
/*
The output of the above codes are:
undefined
ReferenceError: abc is not defined*/

现在在下面的代码中,我们看到了什么?清除了变量和函数表达式。

<script>
var totalAmo = 8;
var getSum = function(a, b){
      return a+b;
}
</script>

但是真实的图片证明了变量和函数都悬挂在该范围的顶部:

console.log(totalAmo);
console.log(getSum(8,9));
var totalAmo = 8;
var getSum = function(a, b){
      return a+b;
}
console.log(totalAmo);
console.log(getSum(9,7));

前两个日志的输出为未定义 TypeError:getSum不是函数,因为var totalAmo getSum 像波纹管一样悬挂在其作用域的顶部

 <script>
        var totalAmo;
        var getSum;

        console.log(totalAmo);
        console.log(getSum(8,9));
        var totalAmo = 8;
        var getSum = function(a, b){
            return a+b;
        }
        console.log(totalAmo);
        console.log(getSum(9,7));
    </script>

但是对于函数声明,整个函数都位于其作用域的顶部。

console.log(getId());
function getId(){
   return 739373;
}
/* output: 739373, because the whole function hoisted on the top of the scope.*/

现在,对于在功能范围内声明的变量,函数表示和函数声明,使用相同的逻辑。 关键点:它们不会被悬挂在文件顶部

function functionScope(){
            var totalAmo;
            var getSum;

            console.log(totalAmo);
            console.log(getSum(8,9));
            var totalAmo = 8;
            var getSum = function(a, b){
                return a+b;
            }
        }

因此,当您使用 var 关键字时,变量和函数将悬挂在该作用域(全局作用域和函数作用域)的顶部。 关于 let const ,const和let仍然像var一样都知道全局范围和函数范围,但是const和let变量也知道另一个范围称为封锁范围。每当有代码块(例如for循环,if else语句,while循环等)时,都会出现一个块作用域。

当我们使用const并在这些块范围内声明变量时,变量声明将仅被提升在其所在的那个块的顶部,而不会被提升在父函数或它悬挂在全球范围的顶端。

 function getTotal(){
            let total=0;
            for(var i = 0; i<10; i++){
                let valueToAdd = i;
                var multiplier = 2;
                total += valueToAdd*multiplier;
            }
            return total;
        }

示例中的变量将像波纹管一样悬挂

 function getTotal(){
            let total;
            var multiplier;
            total = 0;
            for(var i = 0; i<10; i++){
                let valueToAdd;
                valueToAdd = i;
                multiplier = 2;
                total += valueToAdd*multiplier;
            }
            return total;
        }

答案 17 :(得分:0)

ES5:函数提升和变量提升

function hoisting的优先级比greatervariable hoisting

"use strict";

/**
 *
 * @author xgqfrms
 * @license MIT
 * @copyright xgqfrms
 * @created 2016-06-01
 * @modified
 *
 * @description function-hoisting.js
 * @augments
 * @example
 * @link
 *
 */

(function() {
  const log = console.log;

  var a = 1;
  function b() {
    a = 10;
    log(`local a`, a)
    return;
    // function hoisting priority is greater than variable hoisting
    function a() {}
  }
  b();
  log(`global a`, a);
  // local a 10
  // global a 1
})();



等于

(function() {
  const log = console.log;

  // define "a" in global scope
  var a = 1;
  function b() {
    // define "a" in local scope
    var a ;
    // assign function to a
    a = function () {};
    // overwrites local variable "a"
    a = 10;
    log(`local a`, a);
    return;
  }

  b();
  // log global variable "a"
  log(`global a`, a);

  // local a 10
  // global a 1
})();

起吊的原因

var a = 1;                
//"a" is global scope
function b() {  
   var a = function () {}; 
   //"a" is local scope 
   var x = 12; 
   //"x" is local scope 
   a = 10;
   //global variable "a" was overwrited by the local variable "a"  
   console.log("local a =" + a);
   return console.log("local x = " + x);
}       
b();
// local a =10
// local x = 12
console.log("global a = " + a);
// global a = 1
console.log("can't access local x = \n");
// can't access local x = 
console.log(x);
// ReferenceError: x is not defined

/**
 *  scpope & closure & hoisting (var/function)
 *  
 * 1. scpope : the global var can be access in any place(the whole file scope), local var only can be accessed by the local scope(function/block scope)!
 * Note: if a local variable not using var keywords in a function, it will become a global variable!
 * 
 * 2. closure : a function inner the other function, which can access local scope(parent function) & global scope, howerver it's vars can't be accessed by others! unless, your return it as return value!
 * 
 * 3. hoisting : move all declare/undeclare vars/function to the scope top, than assign the value or null!
 * Note: it just move the declare, not move the value!
 * 
 */

ES6 letconst不存在吊装

(() => {
  const log = console.log;
  log(a)
  // Error: Uncaught ReferenceError: Cannot access 'a' before initialization
  let a = 1;
})();



(() => {
  const log = console.log;
  log(b)
  // Error: Uncaught ReferenceError: Cannot access 'b' before initialization
  const b = 1;
})();

裁判

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const