Javascript函数作用域和提升


89

我刚刚读了本·切里(Ben Cherry)撰写的有关JavaScript作用域和提升的精彩文章,他提供了以下示例:

var a = 1;

function b() {
    a = 10;
    return;

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

使用上面的代码,浏览器将警告“ 1”。

我仍然不确定为什么它返回“ 1”。他说的一些事情让人想到:所有函数声明都被提升到顶部。您可以使用函数作用域变量。仍然没有点击我。

Answers:


120

功能提升意味着将功能移到其作用域的顶部。那是,

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"

2
那么所有的函数声明最终都分配给了一个变量?
dev.e.loper 2011年

15
@ dev.e.loper是的,在Javascript中,函数是一流的对象,就像字符串和数字一样。这意味着它们被定义为变量,可以传递给其他函数,可以存储在数组中,依此类推。
彼得·奥尔森

4
绝对不能“重写”功能体。各种ECMAScript标准明确规定在代码执行开始之前先处理变量和函数声明。也就是说,什么都不会移动,而与执行顺序有关(因此,我不喜欢术语“吊装”,因为它表示移动或重新布置)。在重写的代码中,声明var a应在函数声明之前,而赋值a = 1应在函数声明之后。但是请注意,解析器,令牌解析器,解释器,编译器并未指定实际发生这种情况,这只是等效的。
RobG

3
@RobG当然,我想您可以将描述称为一个“撒谎的孩子”,但最终行为是相同的,无论是按字面意义重新排列代码还是重新排列执行顺序。幕后实际发生的事情更多是学术问题,甚至可能取决于实现。
彼得·奥尔森

7
“此外,在这种情况下,其function a() {}行为与相同var a = function () {};  -这在两种方式上都是错误的:首先,如果有的话,它本来应该是var a = function a() {};(该函数实际上不是匿名的),其次,这两种形式是不可互换的,因为var a = function a() {};只有var a;一部分会被吊起。该a = function a() {};部分仍将在return语句后面。因为原始形式是一个函数声明,而不是一个函数表达式,所以实际上它是一个整体。
user4642212 '16

6

您要记住的是,它在执行函数之前会解析整个函数并解析所有变量声明。所以....

function a() {} 

真的变成

var a = function () {}

var a 强制它进入局部作用域,而变量作用域遍历整个函数,因此全局变量仍为1,因为您已经通过将其设为函数来声明为局部作用域。


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仅在本地范围内发生,而不在全局var中发生。


1
这一行“ var a = function(){};”使所有事情都变得很清楚。基本上JavaScript是动态语言,“ function”也是JavaScript中的对象。
重构

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

因此,全局变量的值保持不变,您会收到警告1


1

function a() { }是一个函数语句,它创建函数a局部变量b
解析函数时将创建变量,无论是否var执行了or或function语句。

a = 10 设置此局部变量。


除非添加(在支持该指令的环境中),否则函数执行时实际上会a = 10全局范围内设置变量。b"use strict"
肖恩·维埃拉

@Sean:否,因为函数语句创建了本地标识符。
SLaks 2011年

...和....你是对的。还没有意识到功能提升的特殊后果。谢谢!
肖恩·维埃拉

1

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

情况1:

function a(){}在下面的正文中包含定义function blogs value of a = 1

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

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

情况二

排除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内部。

两种情况的解释

因为function definition with name propertya与相同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

但是,当我们删除函数b外部的function a(){} definitionvalue of 'a'声明和定义的值时,该值将被覆盖,在情况2中该值将更改为10。该值将被覆盖,因为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 propertyin 更改为function a(){} definition其他名称来进一步澄清我们的疑问,'a'

var a = 1;
function b() {
  a = 10; // here var a is declared and defined locally because it uses a var keyword. 
  return;

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

1

吊装是使我们易于理解的概念。实际发生的情况是首先针对它们的范围进行声明,然后在之后进行赋值(而不是同时进行)。

当进行声明时var a,将function b在该b范围内function a声明。

此函数a将隐藏来自全局范围的变量a。

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

        var a = 1;

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

        function a() { }
    }

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

1
令人好奇的是,即使在codeschool.com的一门课程中,也有如此多的专家提到吊装,不过仅仅是对发生的事情的一种简单的看法,实际上吊装根本没有发生。参考:1)developer.mozilla.org/en-US/docs/Glossary/Hoisting 2)《 JavaScript忍者的秘密》第5章2 / e by约翰·雷西格(John resig),比尔·比伯(bear beault),约瑟夫·马拉斯
josip

1

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

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中的变量进行作用域限定。即使您多次调用同一个函数,对于每次调用,它也会创建自己的执行上下文。因此,每个执行上下文在其变量环境中将具有自己的变量副本。没有共享的变量。



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/


0

scpope&闭合&吊装(var / function)

  1. scpope:全局var可以在任何地方(整个文件范围)访问,局部var只能由本地范围(函数/块范围)访问!
    注意:如果局部变量在函数中不使用var关键字,它将成为全局变量!
  2. 闭包:另一个函数内部的一个函数,它可以访问局部作用域(父函数)和全局作用域,但是其他人不能访问它的vars!除非,您将其作为返回值返回!
  3. 提升:将所有声明/取消声明变量/函数移至作用域顶部,然后分配值或为null!
    注意:只移动声明,不移动值!

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



0

提升在JavaScript中,变量声明是在执行任何代码之前在整个程序中执行的。因此,在代码中的任何地方声明变量等同于在开始时声明它。


0

其全部取决于变量“ a”的范围。让我通过将范围创建为图像来进行说明。

JavaScript在这里将创建3个作用域。

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

在此处输入图片说明

当您调用“警报”方法范围时该时间段明确属于那个全局变量,因此它将仅从全局范围中选择变量“ a”的值(即1)。


0

长帖子!

但这会清除空气!

Java Script的工作方式包括两个步骤:

  1. 编译(可以这么说)-此步骤注册变量和函数声明以及它们各自的范围。它不涉及评估函数表达式:var a = function(){}或变量表达式(例如3xvar x =3;对RHS部分进行评估的情况下进行赋值)。

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

检查以下代码的输出以了解:

//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);

让我们打破它:

  1. 在编译阶段,“ a”将在全局范围内注册为值“ undefined”。“ c”也是如此,其当前值将是“ undefined”,而不是“ function()”。b将在全局范围内将“ ”注册为功能。在b范围内,“ f”将被注册为变量,该变量目前尚不确定,而功能“ d”将被注册。

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

console.log(e)
e = 3;

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

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

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.


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. 函数声明提升

与变量提升不同,功能体或分​​配的值也将被提升。考虑这段代码

 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()将在解释带有其定义的代码时移至顶部(仅在提升函数的情况下),因此anow将具有局部作用域,因此不会影响a的全局作用域,而在函数b()中具有自己的作用域。


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;

0

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

在此处输入图片说明

我假设您是一个初学者,首先要了解正确的吊装,我们已经了解了undefinedReferenceError之间的区别

 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 totalAmogetSum都像下面这样悬挂在它们的作用域顶部

 <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关键字时,变量和函数将悬挂在该作用域(全局作用域和函数作用域)的顶部。关于letconst,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;
        }

abobe示例中的变量将像下面这样悬挂

 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;
        }

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/zh-CN/docs/Web/JavaScript/Reference/Statements/var

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/const

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.