JavaScript中变量的范围是什么?


2011

javascript中变量的范围是什么?它们在函数内部和外部的作用域是否相同?还是有关系吗?另外,如果变量是全局定义的,这些变量将存储在哪里?


4
这里是一个牢记此问题的好链接:“ 解释JavaScript范围和闭包 ”。
全栈软件工程师


2
前面提到的凯尔·辛普森的电子书可用来在Github上阅读,它会告诉你所有你需要知道的关于JavaScript的作用域和闭包。您可以在这里找到它:github.com/getify/You-Dont-Know-JS/blob/master / ...这是“您不知道JS”系列丛书的一部分,这对于每个想知道的人来说都是很棒的有关JavaScript的更多信息。
3rik82'2

Answers:


2534

TLDR

JavaScript具有词汇(也称为静态)作用域和闭包。这意味着您可以通过查看源代码来确定标识符的范围。

四个范围是:

  1. 全球-一切可见
  2. 功能-在功能(及其子功能和块)中可见
  3. 块-在块(及其子块)中可见
  4. 模块-在模块内可见

在全局范围和模块范围的特殊情况之外,使用var(函数范围),let(块范围)和const(块范围)声明变量。标识符声明的大多数其他形式在严格模式下具有块作用域。

总览

范围是代码库中标识符有效的区域。

词汇环境是标识符名称和与其关联的值之间的映射。

范围由词汇环境的链接嵌套组成,嵌套中的每个级别对应于祖先执行上下文的词汇环境。

这些链接的词汇环境形成范围“链”。标识符解析是沿着此链搜索匹配标识符的过程。

标识符解析仅在一个方向上发生:向外。这样,外部词汇环境无法“看到”内部词汇环境。

确定 JavaScript中标识符范围的三个相关因素:

  1. 标识符如何声明
  2. 声明标识符的地方
  3. 处于严格模式还是非严格模式

可以声明标识符的一些方式:

  1. varletconst
  2. 功能参数
  3. 捕获块参数
  4. 函数声明
  5. 命名函数表达式
  6. 全局对象上的隐式定义的属性(即var在非严格模式下丢失)
  7. import 陈述
  8. eval

可以声明一些位置标识符:

  1. 全球背景
  2. 功能体
  3. 普通块
  4. 控制结构的顶部(例如循环,if,while等)
  5. 控制结构体
  6. 模组

声明样式

变种

使用声明的标识符var 具有函数scope,除了直接在全局上下文中声明它们时,在这种情况下,它们作为属性添加到全局对象上并具有全局范围。在eval函数中使用它们有单独的规则。

let和const

使用let和声明的标识符const 具有块作用域,除了直接在全局上下文中声明时(在这种情况下,它们具有全局作用域)。

注:letconstvar 都悬挂。这意味着它们的逻辑定义位置是其包围范围(模块或功能)的顶部。然而,变量声明期运用letconst不能读取或分配给控制之前已经通过声明的点在源代码中。过渡期称为时间盲区。

function f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!

功能参数名称

函数参数名称的作用域为函数主体。注意,这有点复杂。声明为默认参数的函数将关闭参数列表,而不是函数的主体。

函数声明

函数声明在严格模式下具有块作用域,在非严格模式下具有函数作用域。注意:非严格模式是基于不同浏览器古怪的历史实现的一组复杂的紧急规则。

命名函数表达式

命名函数表达式的作用域本身(例如,出于递归目的)。

全局对象上的隐式定义的属性

在非严格模式下,全局对象上的隐式定义的属性具有全局范围,因为全局对象位于范围链的顶部。在严格模式下,不允许这样做。

评估

eval字符串中,使用声明的变量var将放置在当前范围内,或者如果eval间接使用,则用作全局对象的属性。

例子

下面将抛出的ReferenceError因为名字xyz有功能没有意义之外f

function f() {
    var x = 1
    let y = 1
    const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)

下面将抛出的ReferenceError为yz,但不适合x,因为知名度x不被约束块。定义控制结构的体块一样ifforwhile,行为类似。

{
    var x = 1
    let y = 1
    const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope

在下面,x由于var具有功能范围,因此在循环外部可见:

for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)

...由于这种行为,您需要小心关闭使用varin循环声明的变量。x此处声明的变量只有一个实例,并且在逻辑上位于循环之外。

以下打印了5五次,然后在循环外部打印5了第六次console.log

for(var x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop

打印以下内容,undefined因为它们x是块作用域的。回调是异步一对一运行的。新行为let变量意味着每个匿名函数关闭了一个名为不同的变量x(不像它会用做var),所以整数0通过4印:

for(let x = 0; x < 5; ++x) {
    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined

以下内容将不会引发,ReferenceError因为x该块的可见性不受该块的限制。但是它将打印,undefined因为该变量尚未初始化(由于该if语句)。

if(false) {
    var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised

for循环顶部使用声明的变量的let作用域为循环的主体:

for(let x = 0; x < 10; ++x) {} 
console.log(typeof x) // undefined, because `x` is block-scoped

ReferenceError由于x该块的可见性受到块的限制,因此以下内容将引发a :

if(false) {
    let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped

使用的变量声明varletconst都作用域模块:

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

以下将在全局对象上声明属性,因为var在全局上下文中使用声明的变量将作为属性添加到全局对象:

var x = 1
console.log(window.hasOwnProperty('x')) // true

let并且const在全局上下文中不向全局对象添加属性,但仍具有全局范围:

let x = 1
console.log(window.hasOwnProperty('x')) // false

函数参数可以认为是在函数体中声明的:

function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function

捕获块参数的作用域为捕获块主体:

try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block

命名函数表达式仅作用于表达式本身:

(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression

在非严格模式下,全局对象上隐式定义的属性是全局范围的。在严格模式下,您会得到一个错误。

x = 1 // implicitly defined property on the global object (no "var"!)

console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true

在非严格模式下,函数声明具有函数范围。在严格模式下,它们具有块作用域。

'use strict'
{
    function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped

它是如何工作的

范围定义为标识符在其上有效的代码的词法区域。

在JavaScript中,每个功能对象都有一个隐藏的[[Environment]]引用,该引用是对在其中创建它的执行上下文(堆栈框架)的词汇环境的引用。

调用函数时,将调用隐藏[[Call]]方法。此方法创建一个新的执行上下文,并在新的执行上下文和功能对象的词法环境之间建立链接。通过将[[Environment]]功能对象上的值复制到新执行上下文的词法环境上的外部引用字段中,可以完成此操作。

注意,新执行上下文和函数对象的词法环境之间的这种链接称为闭包

因此,在JavaScript中,作用域是通过外部引用在“链”中链接在一起的词法环境实现的。这种词汇环境链称为作用域链,并且通过在链中搜索匹配的标识符来进行标识符解析。

了解更多


280
甚至还不是很全面,但这也许是人们有效了解甚至现代javascript所必需的一组Javascript范围技巧。
三联画

148
一个高度评价的答案,不确定为什么。这只是一堆没有适当说明的示例,然后似乎将原型继承(即属性解析)与作用域链(即变量解析)混淆了。comp.lang.javascript FAQ注释中对范围和属性解析进行了全面(准确)的解释。
RobG 2012年

109
@RobG受到高度评价,因为它尽管对于轻微的分解运算仍然有用,并且可被广泛的程序员理解。您发布的链接虽然对某些专业人士有用,但对于当今大多数使用Javascript的人来说都是难以理解的。随时通过编辑答案来解决任何命名问题。
Triptych 2012年

7
@triptych-我仅编辑答案以解决次要问题,而非主要问题。将“作用域”更改为“属性”将解决错误,但不会在没有非常明显区别的情况下混合继承和作用域。
RobG 2012年

24
如果在外部范围内定义变量,然后使用if语句在函数内部使用相同名称定义变量,即使未到达分支也将重新定义。一个例子-jsfiddle.net/3CxVm
克里斯·S

233

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

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

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


1
范围链是[memory] 闭包的另一个术语...对于那些在这里阅读以学习/使用javascript的人。
2014年

108

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

(我敢肯定,真正的JavaScript程序员可以在其他答案中指出很多细微之处。尤其是我在此页面this上随时了解确切含义。希望这个介绍性链接足以使您入门)


7
我什至害怕开始回答这个问题。作为一名真正的Java程序员,我知道答案很快就会失控。好文章。
三联画

10
@Triptych:我知道您对事情失控的意思,但是无论如何添加答案。我只是通过几次搜索就得到了上述答案...由具有实际经验的人写的答案一定会更好。请更正我的任何答案,但这绝对是错误的!
乔恩·斯基特

4
不知何故,乔恩·斯基特(Jon Skeet)负责我在Stack Overflow上最受欢迎的解答。
三联画'18

75

老派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代码并使用基于浏览器的编译器(例如Traceurbabel-standalone),则可以安全地使用该let语句,但是就性能而言,代码可能不是最佳选择。

  • 如果您正在编写客户端JavaScript代码并使用基于Node的编译器(例如traceur shell脚本Babel),则可以安全地使用该let语句。并且由于您的浏览器仅会知道已转译的代码,因此应限制性能方面的弊端。

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

    以下是一些根本不支持的浏览器let

    • Internet Explorer 10及以下
    • Firefox 43及以下
    • Safari 9及以下
    • Android浏览器4及更低版本
    • Opera 27及以下
    • 丁丁40岁及以下
    • 任何版本的Opera MiniBlackberry浏览器

在此处输入图片说明


如何跟踪浏览器支持

用于将上行的最新概述哪些浏览器支持let在你阅读这个答案时声明,请参阅Can I Use


(*)因为提升了 JavaScript变量,所以可以在声明它们之前初始化和使用全局范围和功能范围的变量。这意味着声明始终在作用域的顶部。


2
“不知道”是一种误导,因为变量是由于提升而在此处声明的。
Oriol

上面的示例具有误导性,在块外未知变量“ i”和“ j”。“变量”仅在该特定块中具有作用域,而不在该块之外。Let还具有其他优点,您无法再次重新声明该变量,并且该变量具有词法作用域。
zakir

1
这很有帮助,谢谢!我认为,具体说明“现代JavaScript”和“老式JavaScript”的含义会更有帮助。我认为这些分别对应于ECMAScript 6 / ES6 / ECMAScript 2015和早期版本?
乔恩·施耐德

1
@JonSchneider:正确!在我说“老式JavaScript”的地方,我很想谈论ECMAScript 5,而在我指“现代JavaScript”的地方,我在谈论ECMAScript 6(又名ECMAScript 2015)。不过,我认为在这里进行详细介绍并不是那么重要,因为大多数人只想知道(1)块范围和功能范围之间的区别,(2)什么浏览器支持块范围和(3)对于他们正在从事的任何项目,今天使用块范围是否安全?因此,我的回答集中在解决这些问题上。
John Slegers

1
@JonSchneider :(续)尽管如此,我还是为那些想了解更多关于过去几年中已向JavaScript添加了哪些功能的人添加了指向ES6 / ES2015上Smashing Magazine文章的链接。可能想知道我所说的“现代JavaScript”是什么。
John Slegers '18

39

这是一个例子:

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

您将要研究闭包,以及如何使用它们来成为私有成员



26

在“ Javascript 1.7”(Mozilla对Javascript的扩展)中,还可以使用let语句声明块作用域变量:

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

2
是的,但是使用安全吗?我的意思是,如果我的代码将在WebKit中运行,我会现实地选择此实现吗?
IgorGanapolsky

10
@Python:不,WebKit不支持let
kennytm 2010年

我猜这唯一有效的用途是,如果您知道所有客户端都将使用Mozilla浏览器,例如公司内部系统。
GazB 2012年

或者,如果您正在使用XUL框架(Mozilla的接口框架)进行编程,则可以使用css,xml和javascript进行构建。
Gerard

1
@GazB甚至是一个可怕的想法!因此,今天您知道您的客户正在使用Mozilla,然后出了一封新备忘录,说明他们现在正在使用其他东西。IE浏览器我们的薪资系统烂的原因...您必须使用IE8,并且永远不要使用IE9或IE10或Firefox或Chrome浏览器,因为它无法正常工作...
buzzsawddog13年

25

Brendan Eich最初设计时,JavaScript范围定义的思想来自HyperCard脚本语言HyperTalk

用这种语言,显示的过程类似于一堆索引卡。有一个称为背景的主卡。它是透明的,可以看作是底层卡片。此基本卡上的所有内容均与位于其上方的卡共享。放在顶部的每张卡都有自己的内容,该内容优先于前一张卡,但如果需要,仍可以访问前一张卡。

这正是JavaScript作用域定义系统的设计方式。它只是具有不同的名称。JavaScript中的卡称为执行上下文ECMA。这些上下文中的每一个都包含三个主要部分。可变环境,词法环境和this绑定。回到卡片参考,词法环境包含堆栈中较低位置的先前卡片的所有内容。当前上下文位于堆栈的顶部,声明的所有内容都将存储在变量环境中。在命名冲突的情况下,可变环境将优先。

此绑定将指向包含的对象。有时范围或执行上下文会在不更改包含对象的情况下发生变化,例如在声明的函数(包含对象可能是window构造函数)中。

这些执行上下文是在控制权转移时创建的。当代码开始执行时,将转移控制权,这主要是从函数执行开始。

这就是技术解释。在实践中,重要的是要记住在JavaScript中

  • 范围从技术上讲是“执行上下文”
  • 上下文构成了存储变量的环境的堆栈
  • 堆栈的顶部优先(底部是全局上下文)
  • 每个函数都创建一个执行上下文(但并不总是新的this绑定)

将其应用于此页面上的先前示例之一(5.“结束”),可以遵循执行上下文堆栈。在此示例中,堆栈中包含三个上下文。它们由外部上下文定义,由var 6调用的立即调用函数中的上下文,以及在var 6的立即调用的函数内部返回的函数中的上下文。

i)外部环境。它具有a = 1的可变环境
ii)IIFE上下文,它具有a = 1的词法环境,但是a = 6的变量环境在堆栈中具有优先权
iii)返回的函数上下文,它具有词法a = 6的环境,这是调用时警报中引用的值。

在此处输入图片说明


17

1)有一个全局范围,一个功能范围以及with和catch范围。通常,变量没有“块”级作用域-with和catch语句将名称添加到其块中。

2)作用域一直由函数嵌套到全局作用域。

3)通过原型链解决属性。with语句将对象属性名称带到with块定义的词法范围内。

编辑:ECMAAScript 6(Harmony)被指定为支持let,我知道chrome允许使用“ harmony”标志,因此也许它确实支持它。

让我们为块级作用域提供支持,但是您必须使用关键字来实现它。

编辑:基于本杰明指出注释中的with和catch语句,我已经编辑了帖子,并添加了更多内容。with和catch语句都将变量引入其各自的块中,这就是块作用域。这些变量是传递给它们的对象的属性的别名。

 //chrome (v8)

 var a = { 'test1':'test1val' }
 test1   // error not defined
 with (a) { var test1 = 'replaced' }
 test1   // undefined
 a       // a.test1 = 'replaced'

编辑:澄清示例:

test1的作用域为with块,但别名为a.test1。“ Var test1”在上层词法上下文(函数或全局)中创建一个新变量test1,除非它是-的属性。

kes!小心使用'with'-就像var是noop,如果变量已经在函数中定义,就从对象导入的名称而言,它也是noop!对已经定义的名称稍加注意将使此操作更加安全。因此,我个人将永远不会使用。


您在这里有一些错误,因为一种JavaScript确实具有块作用域的形式。
Benjamin Gruenbaum 2013年

我的耳朵睁开了,本杰明(Benjamin)-以上是我对Javascript范围界定的看法,但它们并非基于阅读规范。我希望您不要指的是with语句(这是对象作用域的一种形式),也不是Mozilla特殊的“ let”语法。
杰拉德·奥尼尔

好的,with语句块作用域的一种形式,但是catch子句是一种更常见的形式(有趣的是,v8 catch用a 实现with)-这几乎是JavaScript本身中块作用域的唯一形式(即,函数,全局,try / catch ,以及它们的派生词),但是主机环境具有不同的作用域概念-例如浏览器和NodeJS的vm模块中的内联事件。
Benjamin Gruenbaum 2013年

本杰明-从我所见,with和catch都仅将对象引入当前范围(以及属性),但是随后在各个块结束之后,将变量重置。但是例如,在catch中引入的新变量将具有封闭函数/方法的范围。
2013年

2
这正是区块范围界定的意思:)
本杰明·格伦鲍姆

9

我发现许多不熟悉JavaScript的人都难以理解,语言默认情况下可以继承,并且函数作用域是迄今为止唯一的作用域。我提供了我去年年底写的名为JSPretty的美化工具的扩展。要素颜色在代码中作用域,并且始终将颜色与该作用域中声明的所有变量关联。当一个颜色的变量来自一个范围时,在另一个范围中使用可视化的方式显示了闭包。

请尝试以下功能:

观看演示:

在以下位置查看代码:

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


1
在Firefox 26中不适用于我。我粘贴代码或加载文件,单击“执行”,但没有任何反应。
mplwork 2014年

范围和继承是两个不同的东西。
Ben Aston

9

JavaScript只有两种类型的作用域:

  1. 全局范围:全局不过是窗口级别的范围,此处整个应用程序中都存在变量。
  2. 功能范围:在具有var关键字的函数中声明的变量具有功能范围。

每当调用函数时,都会创建一个变量作用域对象(并将其包含在作用域链中),然后在JavaScript中跟随该变量。

        a = "global";
         function outer(){ 
              b = "local";
              console.log(a+b); //"globallocal"
         }
outer();

范围链->

  1. 窗位- aouter功能是在作用域链顶层。
  2. 当外部函数称为新函数variable scope object(并包含在作用域链中)并b在其中添加了变量时。

现在,当a需要一个变量时,它首先搜索最近的变量作用域,如果不存在该变量,它将移动到变量作用域链的下一个对象(在这种情况下是窗口级)。


1
不知道为什么这不是可接受的答案。实际上只有功能范围(在ECMA6之前没有“本地范围”)和全局绑定
texasbruce

9

只是为了增加其他答案,范围是所有已声明的标识符(变量)的查找列表,并针对当前执行的代码如何访问使用了一组严格的规则。该查找可能出于分配给变量的目的,该变量是LHS(左侧)引用,或者出于检索其值的目的,其是RHS(右侧)引用。这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作。

因此,从这个角度来看,我认为在Kyle Simpson的“范围和闭包”电子书中找到的图片会有所帮助:

图片

从他的电子书中引用:

该建筑物表示我们程序的嵌套作用域规则集。无论您身在何处,建筑物的第一层都代表您当前正在执行的范围。该建筑的顶层是全球范围。您可以通过查看当前楼层来解析LHS和RHS参考,如果找不到,则将电梯带到下一层,在那儿查看,然后在下一层,依此类推。一旦到达顶层(全局范围),您就可以找到所需的内容,也可以不找到所需的内容。但是无论如何你都必须停下来。

值得注意的一件事是,“示波器在找到第一个匹配项后即停止搜索”。

“作用域级别”的概念解释了为什么如果在嵌套函数中查找“ this”可以用新创建的作用域进行更改。这是进入所有这些详细信息的链接,您想了解有关javascript范围的一切


8

运行代码。希望这会给有关范围界定的想法

Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
    Name: 'object data',
    f: function(){
        alert(this.Name);
    }
};

myObj.newFun = function(){
    alert(this.Name);
}

function testFun(){
    alert("Window Scope : " + window.Name + 
          "\nLocal Scope : " + Name + 
          "\nObject Scope : " + this.Name + 
          "\nCurrent document Scope : " + document.Name
         );
}


testFun.call(myObj);
})(window,document);

8

全球范围:

全局变量就像全局明星一样(成龙,纳尔逊·曼德拉)。您可以从应用程序的任何部分访问它们(获取或设置值)。全局功能就像全局事件(新年,圣诞节)。您可以从应用程序的任何部分执行(调用)它们。

//global variable
var a = 2;

//global function
function b(){
   console.log(a);  //access global variable
}

当地范围:

如果您在美国,可能会认识臭名昭著的名人金·卡戴珊(Kim Kardashian)(她设法制作了小报)。但是美国以外的人不会认出她。她是当地的明星,一定会进入她的领土。

局部变量就像局部恒星。您只能在范围内访问它们(获取或设置值)。局部函数就像局部事件-您只能在该范围内执行(庆祝)。如果要从范围之外访问它们,则会得到参考错误

function b(){
   var d = 21; //local variable
   console.log(d);

   function dog(){  console.log(a); }
     dog(); //execute local function
}

 console.log(d); //ReferenceError: dddddd is not defined    

查看本文以深入了解范围


6

ALMOST只有两种类型的JavaScript范围:

  • 每个var声明的范围都与最直接封闭的函数相关联
  • 如果没有用于var声明的封闭函数,则为全局范围

因此,除功能以外的任何块都不会创建新的作用域。这就解释了为什么for循环会覆盖外部作用域变量:

var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5

使用函数代替:

var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10

在第一个示例中,没有块作用域,因此最初声明的变量被覆盖。在第二个示例中,由于该函数而存在新作用域,因此最初声明的变量为SHADOWED,并且不会被覆盖。

就JavaScript作用域而言,几乎是您需要了解的所有内容,除了:

因此,您可以看到JavaScript范围界定实际上非常简单,尽管并不总是直观的。需要注意的几件事:

  • var声明被提升到作用域的顶部。这意味着无论var声明发生在何处,对于编译器而言,就像var本身发生在顶部一样
  • 合并同一范围内的多个var声明

所以这段代码:

var i = 1;
function abc() {
  i = 2;
  var i = 3;
}
console.log(i);     // outputs 1

等效于:

var i = 1;
function abc() {
  var i;     // var declaration moved to the top of the scope
  i = 2;
  i = 3;     // the assignment stays where it is
}
console.log(i);

这看似与直觉相反,但是从命令式语言设计者的角度来看这是有道理的。


5

现代Js,ES6 +,“ const”和“ let

就像大多数其他主要语言一样,您应该对创建的每个变量使用块作用域。var已经过时了。这使您的代码更安全,更可维护。

const应该用于95%的情况。它使得变量引用无法更改。数组,对象和DOM节点属性可以更改,并且应该更改为const

let应该用于期望重新分配的任何变量。这包括在for循环中。如果您在初始化后更改了值,请使用let

块作用域意味着该变量将仅在声明该变量的方括号内可用。这扩展到内部范围,包括在您的范围内创建的匿名函数。


3

试试这个奇怪的例子。在下面的示例中,如果a是初始化为0的数字,则将看到0,然后是1。除了a是一个对象,并且javascript会将f的指针(而不是其副本)传递给f1。结果是您两次都收到相同的警报。

var a = new Date();
function f1(b)
{
    b.setDate(b.getDate()+1);
    alert(b.getDate());
}
f1(a);
alert(a.getDate());

3

JS中只有函数作用域。不阻止范围!您也可以看到正在起吊的东西。

var global_variable = "global_variable";
var hoisting_variable = "global_hoist";

// Global variables printed
console.log("global_scope: - global_variable: " + global_variable);
console.log("global_scope: - hoisting_variable: " + hoisting_variable);

if (true) {
    // The variable block will be global, on true condition.
    var block = "block";
}
console.log("global_scope: - block: " + block);

function local_function() {
    var local_variable = "local_variable";
    console.log("local_scope: - local_variable: " + local_variable);
    console.log("local_scope: - global_variable: " + global_variable);
    console.log("local_scope: - block: " + block);
    // The hoisting_variable is undefined at the moment.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);

    var hoisting_variable = "local_hoist";
    // The hoisting_variable is now set as a local one.
    console.log("local_scope: - hoisting_variable: " + hoisting_variable);
}

local_function();

// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: " + local_variable);

(答案发布以来已久)阻止范围;developer.mozilla.org/en/docs/Web/JavaScript/Reference/...
鲍勃

2

我的理解是有3个范围:全局范围,全局可用;局部作用域,可用于整个功能,而不受块的影响;和块作用域,仅对使用它的块,语句或表达式可用。全局和局部作用域在函数内或外部用关键字“ var”表示,而块作用域用关键字“ let”表示。

对于那些相信只有全局和局部作用域的用户,请解释为什么Mozilla会有整个页面描述JS中块作用域的细微差别。

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


2

前端编码器经常遇到的一个尚未描述的非常普遍的问题是HTML中的内联事件处理程序可见的范围-例如,

<button onclick="foo()"></button>

on*属性可以引用的变量的范围必须为:

  • 全局(工作的内联处理程序几乎总是引用全局变量)
  • 文档的属性(例如,querySelector独立变量将指向document.querySelector;罕见)
  • 处理程序附加到的元素的属性(如上;罕见)

否则,在调用处理程序时会收到ReferenceError。因此,例如,如果内联处理程序引用 window.onload或中定义的函数,则$(function() {该引用将失败,因为内联处理程序只能引用全局范围内的变量,而该函数不是全局的:

document附加到处理程序的元素的属性和属性也可以称为内联处理程序中的独立变量,因为内联处理程序是两个with(一个用于document,一个用于该元素)内调用的。这些处理程序中的变量作用域链是非常不直观的,并且工作事件处理程序可能会要求函数是全局的(并且应该避免不必要的全局污染)。

由于内联处理程序中的作用域链是如此奇怪,并且内联处理程序需要全局污染才能正常工作,并且内联处理程序有时在传递参数时有时需要丑陋的字符串转义,因此避免它们可能更容易。而是使用Javascript(例如,使用addEventListener)而不是HTML标记附加事件处理程序。


值得一提的是,与<script>在顶层运行的普通标签不同,ES6模块内部的代码在其自己的私有范围内运行。在普通<script>标签顶部定义的变量是全局变量,因此您可以在其他<script>标签中引用它,如下所示:

但是,ES6模块的顶层不是全局的。在ES6模块顶部声明的变量仅在该模块内部可见,除非已明确export编辑该变量,或者除非已将该变量分配给全局对象的属性。

ES6模块的顶层与正常情况下顶层IIFE的内部相似<script>。该模块可以引用任何全局变量,除非该模块是专门为其设计的,否则任何模块都不能引用该模块中的任何内容。


1

在JavaScript中,作用域有两种:

  • 当地范围
  • 全球范围

以下函数具有局部作用域变量carName。而且该变量不能从函数外部访问。

function myFunction() {
    var carName = "Volvo";
    alert(carName);
    // code here can use carName
}

以下类具有全局范围变量carName。而且该变量可从类中的任何地方访问。

class {

    var carName = " Volvo";

    // code here can use carName

    function myFunction() {
        alert(carName);
        // code here can use carName 
    }
}

1

ES5 和更早的:

Javascript中的变量最初在ES6词法作用域之前(范围内)。词法范围是指您可以通过“查看”代码来查看变量的范围。

var关键字声明的每个变量都作用于函数。但是,如果在该函数中声明了其他函数,则这些函数将有权访问外部函数的变量。这称为范围链。它以以下方式工作:

  1. 当一个函数试图解析一个变量值时,它首先会考虑它自己的范围。这是函数主体,即大括号{}之间的所有内容(此函数范围内其他 函数内部的变量除外)。
  2. 如果在函数体内找不到变量,它将爬到链上,并在定义函数的位置查看函数中的变量范围。这就是词汇作用域的含义,我们可以在代码中看到定义此函数的位置,因此仅查看代码即可确定作用域链。

例:

// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';

function outerFunc () {
 // outerFunc scope
 var foo = 'outerFunc';
 var foobar = 'outerFunc';
 innerFunc();
 
 function innerFunc(){
 // innerFunc scope
  var foo = 'innerFunc';
  console.log(foo);
  console.log(bar);
  console.log(foobar);
  }
}

outerFunc();

当我们尝试登录的变量会发生什么foobar以及foobar到控制台如下:

  1. 我们尝试将foo登录到控制台,可以在函数innerFunc本身中找到foo 。因此,foo的值解析为string innerFunc
  2. 我们尝试将bar登录到控制台,但在函数innerFunc本身内部找不到bar 。因此,我们需要攀登范围链。我们首先查看innerFunc定义该函数的外部函数。这就是功能outerFunc。在outerFunc我们的范围内,我们可以找到变量bar,其中包含字符串“ outerFunc”。
  3. 在innerFunc中找不到foobar。。因此,我们需要将范围链爬到 innerFunc范围。在这里也找不到它,我们将范围扩展到了全局范围(即最外部的范围)。我们在这里找到变量foobar,其中包含字符串“ global”。如果在爬取作用域链之后找不到变量,则JS引擎将抛出referenceError

ES6 (ES 2015)及更高版本:

词汇范围和作用域链的相同概念仍然适用于ES6。但是,引入了一种声明变量的新方法。有以下几种:

  • let:创建一个块范围变量
  • const:创建一个块范围的变量,该变量必须初始化并且不能重新分配

之间最大的区别varlet/ constvar是函数作用域而let/ const是块作用域。这是说明此的示例:

let letVar = 'global';
var varVar = 'global';

function foo () {
  
  if (true) {
    // this variable declared with let is scoped to the if block, block scoped
    let letVar = 5;
    // this variable declared with let is scoped to the function block, function scoped
    var varVar = 10;
  }
  
  console.log(letVar);
  console.log(varVar);
}


foo();

在上面的示例中,letVar记录全局值,因为用声明的变量let是块作用域的。它们不再存在于各自的块外,因此无法在if块外访问变量。


0

在EcmaScript5中,主要有两个范围,本地范围全局范围,但是在EcmaScript6中,我们主要有三个范围,本地范围,全局范围和称为块范围的新范围

块范围的示例是:

for ( let i = 0; i < 10; i++)
{
 statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.
}

0

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

0

我真的很喜欢被接受的答案,但我想添加以下内容:

Scope收集并维护所有已声明的标识符(变量)的查找列表,并对当前执行的代码如何访问使用严格的规则集。

范围是用于通过变量标识符名称查找变量的一组规则。

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

-3

JavaScript有两种类型的作用域。

  1. 全局范围:在全局范围内声明的变量可以在程序中的任何位置非常平滑地使用。例如:

    var carName = " BMW";
    
    // code here can use carName
    
    function myFunction() {
         // code here can use carName 
    }
  2. 功能范围或局部范围:在此范围中声明的变量只能在其自己的函数中使用。例如:

    // code here can not use carName
    function myFunction() {
       var carName = "BMW";
       // code here can use carName
    }
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.