题 JavaScript中的变量范围是什么?


javascript中变量的范围是什么?它们的内部是否与函数外部相同?或者甚至重要吗?此外,如果全局定义变量,那么它们存储在哪里?


1715
2018-02-01 08:27


起源


这是另一个不错的 链接 记住这个问题:“解释JavaScript范围和闭包”。 - Full-Stack Software Engineer
这篇文章很好地解释了它。 关于Javascript变量范围你需要知道的一切 - Saurab Parakh
该 前面提到的 可以在Github上阅读Kyle Simpson的电子书,它会告诉您有关JavaScript范围和闭包的所有信息。你可以在这里找到它: github.com/getify/You-Dont-Know-JS/blob/master/... 这是其中的一部分 “你不懂JS”系列丛书,这对于想要了解更多JavaScript的人来说非常棒。 - 3rik82


答案:


我认为我能做的最好的事情就是给你一些学习的例子。 Javascript程序员实际上根据他们理解范围的程度进行排名。 它有时可能非常违反直觉。

  1. 全局范围的变量

    // global scope
    var a = 1;
    
    function one() {
      alert(a); // alerts '1'
    }
    
  2. 本地范围

    // global scope
    var a = 1;
    
    function two(a) {
      // local scope
      alert(a); // alerts the given argument, not the global value of '1'
    }
    
    // local scope again
    function three() {
      var a = 3;
      alert(a); // alerts '3'
    }
    
  3. 中间没有JavaScript中的块范围 (ES5; ES6介绍 let

    一个。

    var a = 1;
    
    function four() {
      if (true) {
        var a = 4;
      }
    
      alert(a); // alerts '4', not the global value of '1'
    }
    

    var a = 1;
    
    function one() {
      if (true) {
        let a = 4;
      }
    
      alert(a); // alerts '1' because the 'let' keyword uses block scoping
    }
    
  4. 中间对象属性

    var a = 1;
    
    function Five() {
      this.a = 5;
    }
    
    alert(new Five().a); // alerts '5'
    
  5. 高级关闭

    var a = 1;
    
    var six = (function() {
      var a = 6;
    
      return function() {
        // JavaScript "closure" means I have access to 'a' in here,
        // because it is defined in the function in which I was defined.
        alert(a); // alerts '6'
      };
    })();
    
  6. 高级基于原型的范围解析

    var a = 1;
    
    function seven() {
      this.a = 7;
    }
    
    // [object].prototype.property loses to
    // [object].property in the lookup chain. For example...
    
    // Won't get reached, because 'a' is set in the constructor above.
    seven.prototype.a = -1;
    
    // Will get reached, even though 'b' is NOT set in the constructor.
    seven.prototype.b = 8;
    
    alert(new seven().a); // alerts '7'
    alert(new seven().b); // alerts '8'
    

  7. 环球+本地一个额外复杂的案例

    var x = 5;
    
    (function () {
        console.log(x);
        var x = 10;
        console.log(x); 
    })();
    

    这将打印出来 undefined 和 10 而不是 5 和 10 因为JavaScript总是将变量声明(而不是初始化)移动到作用域的顶部,使代码等效于:

    var x = 5;
    
    (function () {
        var x;
        console.log(x);
        x = 10;
        console.log(x); 
    })();
    
  8. Catch子句范围的变量

    var e = 5;
    console.log(e);
    try {
        throw 6;
    } catch (e) {
        console.log(e);
    }
    console.log(e);
    

    这将打印出来 565。在catch子句中 e 阴影全局变量和局部变量。但是这个特殊范围仅适用于捕获的变量。如果你写 var f; 在catch子句中,它与在try-catch块之前或之后定义它完全相同。


2267
2018-02-01 08:58



甚至不是很全面,但这可能是必须知道的一套Javascript范围技巧,有效甚至阅读现代的JavaScript。 - Triptych
一个高度评价的答案,不知道为什么。这只是一堆没有正确解释的例子,然后似乎混淆了原型继承(即属性解析)和范围链(即可变分辨率)。范围和属性解析的全面(和准确)解释在comp.lang.javascript中 常见问题解答。 - RobG
@RobG它受到高度评价,因为它对于广泛的程序员来说是有用的和易于理解的,尽管有小的catachresis。您发布的链接虽然对某些专业人员有用,但对于今天编写Javascript的大多数人来说都是不可理解的。通过编辑答案,随意修复任何术语问题。 - Triptych
@ triptych-我只编辑修复小问题的答案,而不是主要的。将“范围”更改为“属性”将修复错误,但不会在没有明确区分的情况下混合继承和范围的问题。 - RobG
如果在外部作用域中定义一个变量,然后使用if语句在函数内部定义一个具有相同名称的变量, 即使没有达到分支 它被重新定义。一个例子 - jsfiddle.net/3CxVm - Chris S


Javascript使用范围链来为给定函数建立范围。通常有一个全局范围,每个定义的函数都有自己的嵌套范围。在另一个函数中定义的任何函数都有一个链接到外部函数的局部作用域。始终是源中定义范围的位置。

范围链中的元素基本上是一个带有指向其父范围的指针的Map。

解析变量时,javascript从最里面的范围开始并向外搜索。


219
2018-02-01 08:35



范围链是[记忆]的另一个术语 关闭...对于那些在这里阅读学习/进入javascript的人。 - New Alexandria


全球声明的变量具有全局范围。在函数内声明的变量作用于该函数,并且阴影全局变量具有相同名称。

(我确信真正的JavaScript程序员可以在其他答案中指出很多细微之处。特别是我遇到过 这一页 究竟是什么 this 意思是在任何时候。希望 这个更多的介绍性链接 足以让你开始。)


93
2018-02-01 08:31



我甚至害怕开始回答这个问题。作为一名真正的Javascript程序员,我知道答案的速度有多快。好文章。 - Triptych
@Triptych:我知道你对失控的事情的意思,但是 请 无论如何都要添加答案。我通过做几次搜索得到了上述内容......有实际经验的人写的答案是 界 更好。请更正我的任何答案,这绝对是错误的! - Jon Skeet
不知何故Jon Skeet负责我在Stack Overflow上最受欢迎的答案。 - Triptych


老派JavaScript

传统上,JavaScript实际上只有两种类型的范围:

  1. 全球范围 :从应用程序开始,整个应用程序都知道变量 (*)
  2. 功能范围 :变量是众所周知的 功能 它们从函数的开头声明 (*)

我不会详细说明这一点,因为已经有许多其他答案解释了这一点。


现代JavaScript

最新的JavaScript规范 现在还允许第三个范围:

  1. 块范围 :变量是众所周知的 从他们宣布开始的那一刻起,他们就被宣布进入 (**)

如何创建块范围变量?

传统上,您可以像这样创建变量:

var myVariable = "Some text";

块范围变量的创建方式如下:

let myVariable = "Some text";

那么功能范围和块范围有什么区别?

要了解功能范围和块范围之间的区别,请考虑以下代码:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

在这里,我们可以看到我们的变量 j 只在第一个for循环中知道,但不在之前和之后。然而,我们的变量 i 在整个功能中都是众所周知的。

另外,请考虑块范围变量在声明之前是未知的,因为它们没有被提升。您也不允许在同一个块中重新声明相同的块范围变量。这使得块范围变量比全局或功能范围变量更不容易出错,这些变量被提升并且在多个声明的情况下不会产生任何错误。


今天使用块范围变量是否安全?

今天是否安全使用取决于您的环境:

  • 如果您正在编写服务器端JavaScript代码(Node.js的),你可以安全地使用 let 声明。

  • 如果您正在编写客户端JavaScript代码并使用转换器(如 Traceur),你可以安全地使用 let 声明,但是你的代码在性能方面可能不是最优的。

  • 如果您正在编写客户端JavaScript代码而不使用转换器,则需要考虑浏览器支持。

    今天,2016年2月23日,这些是一些不支持的浏览器 let 或只有部分支持:

    • Internet Explorer 10 及以下(不支持)
    • Firefox 43 及以下(不支持)
    • Safari 9 及以下(不支持)
    • Opera Mini 8 及以下(不支持)
    • Android浏览器4 及以下(不支持)
    • 歌剧36 及以下(部分支持)
    • Chome 51 及以下(部分支持)

enter image description here


如何跟踪浏览器支持

有关哪些浏览器支持的最新概述 let 在您阅读此答案时的陈述,请参阅 这个 Can I Use 页


(*)全局和功能范围的变量可以在声明之前初始化和使用,因为JavaScript变量是 悬挂 这意味着声明始终位于范围的顶部。

(**)未提升块范围变量


55
2018-02-23 18:51



“未知”是误导性的,因为该变量是由于吊装而在那里宣布的。 - Oriol
以上示例具有误导性,变量“i”和“j”在块外未知。 'let'变量仅在不在块之外的特定块中具有范围。让我们拥有其他优点,你不能再重新声明变量,而是保持词汇范围。 - zakir
@Oriol:最后得到了改进我的答案和解决悬挂问题。谢谢你指出我的答案需要改进。我也做了一些其他的改进。 - John Slegers
@JonSchneider:正确!在我说“老派JavaScript”的时候,我不得不谈论ECMAScript 5以及我所谓的“现代JavaScript”,我正在谈论ECMAScript 6(又名ECMAScript 2015)。我不认为这里详细说明真的很重要,因为大多数人只想知道(1)块范围和功能范围之间的区别,(2)哪些浏览器支持块范围和(3)对于他们正在进行的任何项目,今天使用块范围是否安全。所以我把重点放在解决这些问题上。 - John Slegers
@JonSchneider :(续)尽管如此,我刚刚在ES6 / ES2015上添加了Smashing Magazine文章的链接,以便那些想要了解更多关于过去几年中添加到JavaScript中的哪些功能的人...其他任何人可能想知道我对“现代JavaScript”的意思。 - John Slegers


这是一个例子:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

您需要研究闭包,以及如何使用它们 私人会员


35
2018-02-01 08:48





根据我的理解,关键是Javascript具有功能级别范围与更常见的C块范围。

这是一篇关于这个主题的好文章。


28
2018-05-15 17:38





在“Javascript 1.7”(Mozilla对Javascript的扩展)中,还可以使用声明块范围变量 let 声明

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4

23
2018-04-06 11:19



是的,但使用安全吗?我的意思是,如果我的代码将在WebKit中运行,我是否会真实地选择此实现? - Igor Ganapolsky
@Python:不,WebKit不支持 let。 - kennytm
我想唯一有效的用途就是如果你知道所有客户都会使用像公司内部系统那样的Mozilla浏览器。 - GazB
或者,如果您使用XUL框架进行编程,则使用css,xml和javascript构建Mozilla的界面框架。 - Gerard ONeill
@GazB即使这是一个可怕的想法!所以今天你知道你的客户正在使用Mozilla然后出来一个新的备忘录,说现在他们正在使用其他东西。 I.E.我们的薪酬系统很糟糕的原因...你必须使用IE8,而不是IE9或IE10或Firefox或Chrome,因为它不会工作... - buzzsawddog