javascript中变量的范围是什么?它们在函数内部的作用域与在函数外部的作用域相同吗?或者这有关系吗?此外,如果变量是全局定义的,那么变量存储在哪里?


当前回答

(函数foo(){console.log(foo)})();console.log(foo类型);//未定义,因为“foo”的作用域是它自己的表达式//但是,像这样(函数foo(){console.log('1:',foo)//函数foofoo=100console.log('2:',foo)//函数foo,不是100,为什么?})()

其他回答

ECMAScript 6引入了let和const关键字。这些关键字可以用来代替var关键字。与var关键字相反,let和const关键字支持在块语句中声明局部范围。

var x = 10
let y = 10
const z = 10
{
  x = 20
  let y = 20
  const z = 20
  {
    x = 30
    // x is in the global scope because of the 'var' keyword
    let y = 30
    // y is in the local scope because of the 'let' keyword
    const z = 30
    // z is in the local scope because of the 'const' keyword
    console.log(x) // 30
    console.log(y) // 30
    console.log(z) // 30
  }
  console.log(x) // 30
  console.log(y) // 20
  console.log(z) // 20
}

console.log(x) // 30
console.log(y) // 10
console.log(z) // 10

我发现许多新接触JavaScript的人很难理解继承在默认情况下在语言中是可用的,而函数作用域是迄今为止唯一的作用域。我为去年年底我写的一个名为JSprety的美化者提供了一个扩展。功能为代码中的函数范围着色,并始终将颜色与该范围中声明的所有变量相关联。当在不同的范围中使用来自一个范围的颜色的变量时,可以直观地演示闭包。

尝试以下功能:

http://prettydiff.com/jspretty.xhtml?c=white&jsscope

观看演示:

http://prettydiff.com/jspretty.xhtml?c=white&jsscope&s=http://prettydiff.com/lib/markup_beauty.js

在以下位置查看代码:

http://prettydiff.com/lib/jspretty.jshttps://github.com/austincheney/Pretty-Diff/blob/master/lib/jspretty.js

目前,该功能支持深度为16个嵌套函数,但目前不为全局变量着色。

我很喜欢接受的答案,但我想补充一点:

作用域收集并维护所有声明的标识符(变量)的查找列表,并对当前执行的代码如何访问这些标识符实施一组严格的规则。

作用域是一组按标识符名称查找变量的规则。

如果在直接作用域中找不到变量,引擎将继续查询下一个外部包含作用域,直到找到或到达最外部(即全局)作用域。是一组规则,用于确定在何处以及如何查找变量(标识符)。该查找可能用于分配变量,该变量是LHS(左侧)参考,也可能用于检索其值,该值是RHS(右侧)参考。LHS引用源于赋值操作。与作用域相关的赋值可以使用=运算符进行,也可以通过将参数传递给(赋值给)函数参数进行。JavaScript引擎在执行代码之前首先编译代码,在这样做的过程中,它会拆分像vara=2这样的语句;分为两个单独的步骤:第一步。首先,var a在该范围中声明它。这是在代码执行之前开始执行的。第二。稍后,a=2查找变量(LHS引用),如果找到,则将其赋值。LHS和RHS引用查找都从当前执行的作用域开始,如果需要的话(也就是说,它们找不到它们在那里寻找的对象),它们会在嵌套作用域中一次一个作用域(层)查找标识符,直到它们到达全局(顶层)并停止,然后要么找到,要么不找到。未完成的RHS引用导致引发ReferenceError。未完成的LHS引用将导致该名称的自动隐式创建的全局(如果不在严格模式下),或ReferenceError(如果在严格模式中)。作用域由一系列“气泡”组成,每个气泡充当容器或桶,其中声明了标识符(变量、函数)。这些气泡整齐地嵌套在彼此内部,这种嵌套是在作者时定义的。

据我理解,关键是Javascript具有函数级作用域,而不是更常见的C块作用域。

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

老式JavaScript

传统上,JavaScript实际上只有两种类型的作用域:

全局范围:从应用程序开始,变量在整个应用程序中都是已知的(*)函数作用域:变量在声明它们的函数中是已知的,从函数开始(*)

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


现代JavaScript

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

块作用域:标识符从其声明的作用域的顶部开始“已知”,但在其声明的行之后才能分配或取消引用(读取)。这一过渡时期被称为“时间死区”


如何创建块范围变量?

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

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只在first for循环中知道,而在前后都不知道。然而,我们的变量i在整个函数中是已知的。

此外,考虑到块范围的变量在声明之前是未知的,因为它们没有被提升。也不允许在同一块内重新声明同一块范围内的变量。这使得块作用域变量比全局或函数作用域变量更不容易出错,这些变量被提升,在多个声明的情况下不会产生任何错误。


现在使用块范围变量安全吗?

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

如果您正在编写服务器端JavaScript代码(Node.js),则可以安全地使用let语句。如果您正在编写客户端JavaScript代码并使用基于浏览器的转译器(如Traceur或babel standalone),则可以安全地使用let语句,但是您的代码在性能方面可能不是最佳的。如果您正在编写客户端JavaScript代码并使用基于节点的转译器(如traceur shell脚本或Babel),则可以安全地使用let语句。而且,由于您的浏览器将只知道转译的代码,因此性能缺陷应该受到限制。如果您正在编写客户端JavaScript代码,而不使用转译器,则需要考虑浏览器支持。以下是一些根本不支持let的浏览器:Internet explorer 10及以下Firefox 43及以下Safari 9及以下Android浏览器4及以下版本Opera 27及以下Chome 40及以下Opera Mini和黑莓浏览器的任何版本


如何跟踪浏览器支持

有关阅读此答案时哪些浏览器支持let语句的最新概述,请参阅“我可以使用”页面。


(*)全局和函数范围的变量可以在声明之前初始化和使用,因为JavaScript变量被提升。这意味着声明总是位于范围的顶部。