JavaScript 中变量的作用域是什么?
javascript中变量的作用域是什么? 它们在函数内部和外部具有相同的作用域吗? 或者说这有什么关系吗? 另外,如果全局定义变量,它们存储在哪里?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
javascript中变量的作用域是什么? 它们在函数内部和外部具有相同的作用域吗? 或者说这有什么关系吗? 另外,如果全局定义变量,它们存储在哪里?
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
接受
或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
发布评论
评论(27)
内联处理程序
前端编码人员经常遇到的一个尚未描述的非常常见的问题是 HTML 中内联事件处理程序可见的范围 - 例如,
on* 属性可以引用必须 可以是:
querySelector
作为独立变量将指向document.querySelector
;罕见)否则,在调用处理程序时您将得到一个ReferenceError。 因此,例如,如果内联处理程序引用内部定义的函数
window.onload
或$(function() {
,则引用将会失败,因为内联处理程序可能只引用全局范围内的变量,并且该函数不是全局的:document
的属性和处理程序所附加的元素的属性也可以在内联处理程序中作为独立变量进行引用,因为内联处理程序被调用 内部两个with
块,一个用于文档
,一个用于元素。 这些处理程序内的变量作用域链极其不直观,并且工作的事件处理程序可能需要函数是全局的(并且应该避免不必要的全局污染应该避免)。由于内联处理程序内的作用域链非常奇怪,并且由于内联处理程序需要全局污染才能工作,并且由于内联处理程序有时在传递参数时需要丑陋的字符串转义,因此可能更容易避免它们。 相反,使用 Javascript 附加事件处理程序(例如使用
addEventListener
),而不是使用 HTML 标记。模块 (
)
另一方面,与正常的
标签不同,它在顶层运行,代码在 ES6 内部模块在其自己的私有范围内运行。 在普通
标记顶部定义的变量是全局变量,因此您可以在其他
标记中引用它,如下所示:
但 ES6 模块的顶层不是全局的。 在 ES6 模块顶部声明的变量仅在该模块内部可见,除非该变量被显式导出,或者除非它被分配给全局对象的属性。
ES6 模块的顶层类似于普通
顶层 IIFE 内部的顶层。 模块可以引用任何全局变量,并且没有任何东西可以引用模块内部的任何内容,除非模块是明确设计的。
Inline handlers
A very common issue not described yet that front-end coders often run into is the scope that is visible to an inline event handler in the HTML - for example, with
The scope of the variables that an
on*
attribute can reference must be either:querySelector
as a standalone variable will point todocument.querySelector
; rare)Otherwise, you'll get a ReferenceError when the handler is invoked. So, for example, if the inline handler references a function which is defined inside
window.onload
or$(function() {
, the reference will fail, because the inline handler may only reference variables in the global scope, and the function is not global:Properties of the
document
and properties of the element the handler is attached to may also be referenced as standalone variables inside inline handlers because inline handlers are invoked inside of twowith
blocks, one for thedocument
, one for the element. The scope chain of variables inside these handlers is extremely unintuitive, and a working event handler will probably require a function to be global (and unnecessary global pollution should probably be avoided).Since the scope chain inside inline handlers is so weird, and since inline handlers require global pollution to work, and since inline handlers sometimes require ugly string escaping when passing arguments, it's probably easier to avoid them. Instead, attach event handlers using Javascript (like with
addEventListener
), rather than with HTML markup.Modules (
<script type="module">
)On a different note, unlike normal
<script>
tags, which run on the top level, code inside ES6 modules runs in its own private scope. A variable defined at the top of a normal<script>
tag is global, so you can reference it in other<script>
tags, like this:But the top level of an ES6 module is not global. A variable declared at the top of an ES6 module will only be visible inside that module, unless the variable is explicitly
export
ed, or unless it's assigned to a property of the global object.The top level of an ES6 module is similar to that of the inside of an IIFE on the top level in a normal
<script>
. The module can reference any variables which are global, and nothing can reference anything inside the module unless the module is explicitly designed for it.我发现许多 JavaScript 新手很难理解该语言默认情况下可以使用继承,并且到目前为止,函数作用域是唯一的作用域。 我为去年年底编写的一个名为 JSPretty 的美化器提供了一个扩展。 颜色功能在代码中起作用,并且始终将颜色与该范围内声明的所有变量相关联。 当具有来自一个范围的颜色的变量在不同的范围中使用时,可以直观地演示闭包。
尝试以下位置的功能:
请参阅演示:
查看代码:
目前,该功能支持 16 个嵌套函数的深度,但目前不为全局变量着色。
I found that many people new to JavaScript have trouble understanding that inheritance is available by default in the language and that function scope is the only scope, so far. I provided an extension to a beautifier I wrote at the end of last year called JSPretty. The feature colors function scope in the code and always associates a color to all variables declared in that scope. Closure is visually demonstrated when a variable with a color from one scope is used in a different scope.
Try the feature at:
See a demo at:
View the code at:
Currently the feature offers support for a depth of 16 nested functions, but currently does not color global variables.
JavaScript 只有两种类型的作用域:
var
关键字声明的变量具有函数作用域。每当调用函数时,都会创建一个变量作用域对象(并包含在作用域链中),后面跟着 JavaScript 中的变量。
作用域链 -->
a
和outer
函数位于作用域链的顶层。现在,当变量
a
需要时,它首先搜索最近的变量范围,如果变量不存在,则它移动到变量范围链的下一个对象。在本例中是窗口级别。JavaScript have only two type of scope :
var
keyword has functional scope.Whenever a function is called, a variable scope object is created (and included in scope chain) which is followed by variables in JavaScript.
Scope chain -->
a
andouter
function are at top level in scope chain.variable scope object
(and included in scope chain) added with variableb
inside it.Now when a variable
a
required it first searches for nearest variable scope and if variable is not there than it move's to next object of variable scope chain.which is in this case is window level.运行代码。 希望这能提供有关范围界定的想法
run the code. hope this will give an idea about scoping
全局范围:
全局变量就像全球明星(成龙、纳尔逊·曼德拉)一样。 您可以从应用程序的任何部分访问它们(获取或设置值)。 全球职能就像全球活动(新年、圣诞节)。 您可以从应用程序的任何部分执行(调用)它们。
本地范围:
如果您在美国,您可能认识金·卡戴珊(Kim Kardashian),臭名昭著的名人(她以某种方式设法制作小报)。 但美国以外的人不会认出她。 她是当地的明星,绑定在她的领土上。
局部变量就像局部星星。 您只能在范围内访问它们(获取或设置值)。 本地函数就像本地事件 - 您只能在该范围内执行(庆祝)。 如果您想从范围之外访问它们,您将收到参考错误
查看这篇文章深入了解范围
Global Scope :
Global variables are exactly like global stars (Jackie Chan, Nelson Mandela). You can access them (get or set the value), from any part of your application. Global functions are like global events (New Year, Christmas). You can execute (call) them from any part of your application.
Local Scope :
If you are in the USA, you may know Kim Kardashian, infamous celebrity ( she somehow manages to make the tabloids). But people outside of the USA will not recognize her. She is a local star, bound to her territory.
Local variables are like local stars. You can only access them (get or set the value) inside the scope. A local function is like local events - you can execute only (celebrate) inside that scope. If you want to access them from outside of the scope, you will get a reference error
Check this article for in-depth understanding of scope
JavaScript 作用域几乎只有两种类型:
因此,除函数之外的任何块都不会创建新的范围。 这解释了为什么 for 循环会覆盖外部作用域变量:
使用函数代替:
在第一个示例中,没有块作用域,因此最初声明的变量被覆盖。 在第二个示例中,由于该函数存在新的作用域,因此最初声明的变量被隐藏,并且没有被覆盖。
这几乎是您在 JavaScript 作用域方面需要了解的所有内容,除了:
因此您可以看到 JavaScript 作用域实际上非常简单,尽管并不总是直观的。 有几点需要注意:
所以这段代码:
相当于:
这可能看起来违反直觉,但它使得从命令式语言设计者的角度来看。
There are ALMOST only two types of JavaScript scopes:
So, any blocks other than functions do not create a new scope. That explains why for-loops overwrite outer scoped variables:
Using functions instead:
In the first example, there was no block scope, so the initially declared variables were overwritten. In the second example, there was a new scope due to the function, so the initially declared variables were SHADOWED, and not overwritten.
That's almost all you need to know in terms of JavaScript scoping, except:
So you can see JavaScript scoping is actually extremely simple, albeit not always intuitive. A few things to be aware of:
So this code:
is equivalent to:
This may seem counter intuitive, but it makes sense from the perspective of a imperative language designer.
只是为了添加其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则来确定当前执行的代码如何访问这些标识符。 此查找可能是为了分配给变量(LHS(左侧)引用),也可能是为了检索其值(RHS(右侧)引用)。 这些查找是 JavaScript 引擎在编译和执行代码时在内部执行的操作。
因此,从这个角度来看,我认为我在 Kyle Simpson 的《作用域和闭包》电子书中找到的一张图片会有所帮助:
引用他的电子书:
值得一提的是,“一旦找到第一个匹配项,范围查找就会停止”。
“范围级别”的概念解释了为什么“this”可以通过新创建的范围进行更改(如果在嵌套函数中查找)。
这是一个包含所有这些详细信息的链接,您想要的一切了解 javascript 作用域
Just to add to the other answers, scope is a look-up list of all the declared identifiers (variables), and enforces a strict set of rules as to how these are accessible to currently executing code. This look-up may be for the purposes of assigning to the variable, which is an LHS (lefthand-side) reference, or it may be for the purposes of retrieving its value, which is an RHS (righthand-side) reference. These look-ups are what the JavaScript engine is doing internally when it's compiling and executing the code.
So from this perspective, I think that a picture would help that I found in the Scopes and Closures ebook by Kyle Simpson:
Quoting from his ebook:
One thing of note that is worth mentioning, "Scope look-up stops once it finds the first match".
This idea of "scope levels" explains why "this" can be changed with a newly created scope, if it's being looked up in a nested function.
Here is a link that goes into all these details, Everything you wanted to know about javascript scope
现代 Js、ES6+、“
const
”和“let
”您应该为您创建的每个变量使用块作用域,就像大多数其他主要语言一样。
var
已过时。 这使您的代码更安全且更易于维护。const
应该用于95% 的情况。 它使得变量引用无法更改。 数组、对象和 DOM 节点属性可以更改,并且应该是const
。let
应该用于任何需要重新分配的变量。 这包括在 for 循环内。 如果您在初始化之外更改值,请使用let
。块作用域意味着该变量仅在声明它的括号内可用。 这扩展到内部作用域,包括在您的作用域内创建的匿名函数。
Modern Js, ES6+, '
const
' and 'let
'You should be using block scoping for every variable you create, just like most other major languages.
var
is obsolete. This makes your code safer and more maintainable.const
should be used for 95% of cases. It makes it so the variable reference can't change. Array, object, and DOM node properties can change and should likely beconst
.let
should be be used for any variable expecting to be reassigned. This includes within a for loop. If you ever change value beyond initialization, uselet
.Block scope means that the variable will only be available within the brackets in which it is declared. This extends to internal scopes, including anonymous functions created within your scope.
试试这个奇怪的例子。 在下面的示例中,如果 a 是一个初始化为 0 的数字,您会看到 0,然后是 1。除了 a 是一个对象之外,javascript 会向 f1 传递 a 的指针而不是它的副本。 结果是您两次都会收到相同的警报。
Try this curious example. In the example below if a were a numeric initialized at 0, you'd see 0 and then 1. Except a is an object and javascript will pass f1 a pointer of a rather than a copy of it. The result is that you get the same alert both times.
JS 中只有函数作用域。 不是块范围!
您也可以看到正在提升的内容。
There are only function scopes in JS. Not block scopes!
You can see what is hoisting too.
我的理解是有3个作用域:全局作用域,全局可用; 局部作用域,可用于整个函数,无论块如何; 和块作用域,仅适用于使用它的块、语句或表达式。 全局和局部作用域用关键字“var”表示,无论是在函数内部还是外部,块作用域用关键字“let”表示。
对于那些认为只有全局作用域和局部作用域的人,请解释为什么 Mozilla 会有一个完整的页面来描述 JS 中块作用域的细微差别。
https://developer.mozilla.org/en- US/docs/Web/JavaScript/Reference/Statements/let
My understanding is that there are 3 scopes: global scope, available globally; local scope, available to an entire function regardless of blocks; and block scope, only available to the block, statement, or expression on which it was used. Global and local scope are indicated with the keyword 'var', either within a function or outside, and block scope is indicated with the keyword 'let'.
For those that believe there is only global and local scope, please explain why Mozilla would have an entire page describing the nuances of block scope in JS.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
ES5
及更早版本:Javascript 中的变量最初(
ES6
之前)具有词法函数作用域。 术语词法作用域意味着您可以通过“查看”代码来查看变量的作用域。使用
var
关键字声明的每个变量的作用域都是该函数。 但是,如果在该函数内声明了其他函数,这些函数将有权访问外部函数的变量。 这称为作用域链。 它以以下方式工作:例子:
当我们尝试将变量
foo
、bar
和foobar
记录到控制台时,会发生以下情况:innerFunc
本身中找到。 因此,foo 的值被解析为字符串innerFunc
。innerFunc
本身内部找不到 bar。 因此,我们需要攀爬作用域链。 我们首先查看定义了函数innerFunc
的外部函数。 这是函数outerFunc
。 在outerFunc
的范围内,我们可以找到变量bar,它保存字符串“outerFunc”。ES6
(ES 2015) 及更早版本:词法作用域和作用域链的相同概念仍然适用于
ES6
。 然而,引入了声明变量的新方法。 有以下几种:let
:创建块作用域变量const
:创建块作用域变量,必须初始化且不能重新赋值var 之间最大的区别
和let
/const
是var
是函数作用域,而let
/const< /code> 是块作用域的。 下面是一个例子来说明这一点:
在上面的示例中,letVar 记录全局值,因为用
let
声明的变量是块作用域的。 它们不再存在于各自的块之外,因此无法在 if 块之外访问该变量。ES5
and earlier:Variables in Javascript were initially (pre
ES6
) lexically function scoped. The term lexically scoped means that you can see the scope of the variables by 'looking' at the code.Every variable declared with the
var
keyword is scoped to the function. However, if other function are declared within that function those functions will have access to the variables of the outer functions. This is called a scope chain. It works in the following manner:Example:
What happens when we are trying to log the variables
foo
,bar
, andfoobar
to the console is the following:innerFunc
itself. Therefore, the value of foo is resolved to the stringinnerFunc
.innerFunc
itself. Therefore, we need to climb the scope chain. We first look in the outer function in which the functioninnerFunc
was defined. This is the functionouterFunc
. In the scope ofouterFunc
we can find the variable bar, which holds the string 'outerFunc'.ES6
(ES 2015) and older:The same concepts of lexically scope and scopechain still apply in
ES6
. However a new ways to declare variables were introduced. There are the following:let
: creates a block scoped variableconst
: creates a block scoped variable which has to be initialized and cannot be reassignedThe biggest difference between
var
andlet
/const
is thatvar
is function scoped whereaslet
/const
are block scoped. Here is an example to illustrate this:In the above example letVar logs the value global because variables declared with
let
are block scoped. They cease to exist outside their respective block, so the variable can't be accessed outside the if block.在 JavaScript 中,有两种类型的作用域:
下面的函数有一个本地作用域变量
carName
。 并且该变量无法从函数外部访问。下面的类有一个全局范围变量
carName
。 并且这个变量可以从类中的任何地方访问。In JavaScript there are two types of scope:
The Below function has a local scope variable
carName
. And this variable is not accessible from outside of the function.The Below Class has a Global scope variable
carName
. And this variable is accessible from everywhere in the class.我真的很喜欢接受的答案,但我想添加以下内容:
范围收集并维护所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则来确定当前执行的访问方式代码。
范围是一组通过标识符名称查找变量的规则。
I really like the accepted answer but I want to add this:
Scope collects and maintains a look-up list of all the declared identifiers (variables), and enforces a strict set of rules as to how these are accessible to currently executing code.
Scope is a set of rules for looking up variables by their identifier name.
在EcmaScript5中,主要有两个作用域,本地作用域和全局作用域,但在EcmaScript6中,我们主要有三个作用域,本地作用域,全局作用域和一个名为的新作用域块范围。
块作用域的示例是:-
In EcmaScript5, there are mainly two scopes, local scope and global scope but in EcmaScript6 we have mainly three scopes, local scope, global scope and a new scope called block scope.
Example of block scope is :-
ECMAScript 6 引入了 let 和 const 关键字。 这些关键字可以用来代替 var 关键字。 与 var 关键字相反,let 和 const 关键字支持块语句内局部作用域的声明。
ECMAScript 6 introduced the let and const keywords. These keywords can be used in place of the var keyword. Contrary to the var keyword, the let and const keywords support the declaration of local scope inside block statements.
JavaScript 中有两种类型的作用域。
全局范围:在全局范围内声明的变量可以在程序中的任何地方非常顺利地使用。 例如:
函数作用域或局部作用域:在此作用域中声明的变量只能在其自己的函数中使用。 例如:
There are two types of scopes in JavaScript.
Global scope: variable which is announced in global scope can be used anywhere in the program very smoothly. For example:
Functional scope or Local scope: variable declared in this scope can be used in its own function only. For example:
TLDR
JavaScript 具有词法(也称为静态)作用域和闭包。 这意味着您可以通过查看源代码来了解标识符的范围。
这四个范围是:
除了特殊情况外全局和模块作用域,变量使用
var
(函数作用域)、let
(块作用域)和const
(块作用域)声明。 大多数其他形式的标识符声明在严格模式下具有块作用域。概述
范围是标识符有效的代码库区域。
词法环境是标识符名称和与其关联的值之间的映射。
作用域由词法环境的链接嵌套构成,嵌套中的每个级别对应于祖先执行上下文的词法环境。
这些链接的词汇环境形成了范围“链”。 标识符解析是沿着这条链搜索匹配标识符的过程。
标识符解析仅发生在一个方向:向外。 这样,外部词汇环境就无法“看到”内部词汇环境。
决定范围时需要考虑三个相关因素: //www.ecma-international.org/ecma-262/10.0/index.html#sec-names-and-keywords" rel="noreferrer">JavaScript 中的标识符:
声明标识符的一些方式:
var
、let
和const
var
code> 非严格模式)import
语句eval
可以声明一些位置标识符:
声明样式
var
使用
var
声明的标识符 具有函数作用域,除了直接在全局上下文中声明的情况之外,在在这种情况下,它们将作为属性添加到全局对象上并具有全局范围。 它们在 eval 函数中的使用有单独的规则。let 和 const
使用
let
和const
声明的标识符具有块作用域,除非它们直接在全局上下文中声明,在这种情况下它们具有全球范围。注意:
let
、const
和var
都是吊起。 这意味着它们定义的逻辑位置是其封闭范围(块或函数)的顶部。 但是,在控制权通过源代码中的声明点之前,无法读取或分配使用let
和const
声明的变量。 过渡期被称为暂时死区。函数参数名称
函数参数名称的作用域为函数体。 请注意,这有一点复杂。 声明为默认参数的函数会关闭参数列表,而不是函数的主体。
函数声明
函数声明在严格模式下具有块作用域,在非严格模式下具有函数作用域。 注意:非严格模式是一组复杂的紧急规则,基于不同浏览器的古怪历史实现。
命名函数表达式
命名函数表达式的作用域仅限于其自身(例如,为了递归的目的)。
全局对象上隐式定义的属性
在非严格模式下,全局对象上隐式定义的属性具有全局作用域,因为全局对象位于作用域链的顶部。 在严格模式下,这些是不允许的。
eval
在
eval
字符串中,使用var
声明的变量将被放置在当前作用域中,或者,如果间接使用eval
,则作为全局对象。示例
下面将引发 ReferenceError,因为名称
x
、y
和z
在函数f.
以下代码将为
y
和z
抛出 ReferenceError,但不会为x
抛出 ReferenceError,因为x
的可见性为不受块的限制。 定义控制结构主体的块(如if
、for
和while
)的行为类似。在下面的代码中,
x
在循环外部可见,因为var
具有函数作用域:...由于这种行为,您需要小心关闭循环中使用
var
声明的变量。 这里只声明了一个变量x
实例,并且它在逻辑上位于循环之外。以下代码打印
5
五次,然后为循环外的console.log
打印5
第六次:以下内容会打印
undefined
,因为x
是块作用域的。 回调是一对一异步运行的。let
变量的新行为意味着每个匿名函数都会通过名为x
的不同变量关闭(与var
不同),因此打印整数0
到4
。:下面的代码不会抛出
ReferenceError
因为x
的可见性不受块的约束; 但是,它会打印undefined
,因为变量尚未初始化(由于if
语句)。使用
let
在for
循环顶部声明的变量的作用域为循环体:以下代码将抛出
ReferenceError
,因为x
的可见性受到块的限制:使用
var
、let
或const
声明的变量的作用域都是模块:下面将在全局对象上声明一个属性,因为使用
声明的变量全局上下文中的 code>var
作为属性添加到全局对象中:全局上下文中的
let
和const
不会向全局对象添加属性,但仍然具有全局作用域:函数参数可以认为是在函数体中声明:
Catch 块参数的作用域为 catch 块主体:
命名函数表达式的作用域仅限于表达式本身:
在非严格模式下,全局对象上隐式定义的属性具有全局作用域。 在严格模式下,您会收到错误。
在非严格模式下,函数声明具有函数作用域。 在严格模式下,它们具有块作用域。
它是如何工作的
范围被定义为标识符有效的代码的词汇区域。
在 JavaScript 中,每个函数对象都有一个隐藏的
[[Environment]]
引用,它是对 词汇环境 执行上下文(堆栈框架),在其中创建它。当您调用函数时,会调用隐藏的
[[Call]]
方法。 此方法创建一个新的执行上下文,并在新的执行上下文和函数对象的词法环境之间建立链接。 它通过将函数对象上的[[Environment]]
值复制到 新执行上下文的词法环境中的外部引用字段。请注意,新的执行上下文和函数对象的词法环境之间的链接称为闭包。
因此,在 JavaScript 中,作用域是通过外部引用以“链”形式链接在一起的词法环境来实现的。 这个词法环境链称为作用域链,标识符解析通过 在链上搜索以查找匹配的标识符。
了解更多信息。
TLDR
JavaScript has lexical (also called static) scoping and closures. This means you can tell the scope of an identifier by looking at the source code.
The four scopes are:
Outside of the special cases of global and module scope, variables are declared using
var
(function scope),let
(block scope), andconst
(block scope). Most other forms of identifier declaration have block scope in strict mode.Overview
Scope is the region of the codebase over which an identifier is valid.
A lexical environment is a mapping between identifier names and the values associated with them.
Scope is formed of a linked nesting of lexical environments, with each level in the nesting corresponding to a lexical environment of an ancestor execution context.
These linked lexical environments form a scope "chain". Identifier resolution is the process of searching along this chain for a matching identifier.
Identifier resolution only occurs in one direction: outwards. In this way, outer lexical environments cannot "see" into inner lexical environments.
There are three pertinent factors in deciding the scope of an identifier in JavaScript:
Some of the ways identifiers can be declared:
var
,let
andconst
var
in non-strict mode)import
statementseval
Some of the locations identifiers can be declared:
Declaration Styles
var
Identifiers declared using
var
have function scope, apart from when they are declared directly in the global context, in which case they are added as properties on the global object and have global scope. There are separate rules for their use ineval
functions.let and const
Identifiers declared using
let
andconst
have block scope, apart from when they are declared directly in the global context, in which case they have global scope.Note:
let
,const
andvar
are all hoisted. This means that their logical position of definition is the top of their enclosing scope (block or function). However, variables declared usinglet
andconst
cannot be read or assigned to until control has passed the point of declaration in the source code. The interim period is known as the temporal dead zone.Function parameter names
Function parameter names are scoped to the function body. Note that there is a slight complexity to this. Functions declared as default arguments close over the parameter list, and not the body of the function.
Function declarations
Function declarations have block scope in strict mode and function scope in non-strict mode. Note: non-strict mode is a complicated set of emergent rules based on the quirky historical implementations of different browsers.
Named function expressions
Named function expressions are scoped to themselves (e.g., for the purpose of recursion).
Implicitly defined properties on the global object
In non-strict mode, implicitly defined properties on the global object have global scope, because the global object sits at the top of the scope chain. In strict mode, these are not permitted.
eval
In
eval
strings, variables declared usingvar
will be placed in the current scope, or, ifeval
is used indirectly, as properties on the global object.Examples
The following will throw a ReferenceError because the names
x
,y
, andz
have no meaning outside of the functionf
.The following will throw a ReferenceError for
y
andz
, but not forx
, because the visibility ofx
is not constrained by the block. Blocks that define the bodies of control structures likeif
,for
, andwhile
, behave similarly.In the following,
x
is visible outside of the loop becausevar
has function scope:...because of this behavior, you need to be careful about closing over variables declared using
var
in loops. There is only one instance of variablex
declared here, and it sits logically outside of the loop.The following prints
5
, five times, and then prints5
a sixth time for theconsole.log
outside the loop:The following prints
undefined
becausex
is block-scoped. The callbacks are run one by one asynchronously. New behavior forlet
variables means that each anonymous function closed over a different variable namedx
(unlike it would have done withvar
), and so integers0
through4
are printed.:The following will NOT throw a
ReferenceError
because the visibility ofx
is not constrained by the block; it will, however, printundefined
because the variable has not been initialised (because of theif
statement).A variable declared at the top of a
for
loop usinglet
is scoped to the body of the loop:The following will throw a
ReferenceError
because the visibility ofx
is constrained by the block:Variables declared using
var
,let
orconst
are all scoped to modules:The following will declare a property on the global object because variables declared using
var
within the global context are added as properties to the global object:let
andconst
in the global context do not add properties to the global object, but still have global scope:Function parameters can be considered to be declared in the function body:
Catch block parameters are scoped to the catch-block body:
Named function expressions are scoped only to the expression itself:
In non-strict mode, implicitly defined properties on the global object are globally scoped. In strict mode, you get an error.
In non-strict mode, function declarations have function scope. In strict mode, they have block scope.
How it works under the hood
Scope is defined as the lexical region of code over which an identifier is valid.
In JavaScript, every function-object has a hidden
[[Environment]]
reference that is a reference to the lexical environment of the execution context (stack frame) within which it was created.When you invoke a function, the hidden
[[Call]]
method is called. This method creates a new execution context and establishes a link between the new execution context and the lexical environment of the function-object. It does this by copying the[[Environment]]
value on the function-object, into an outer reference field on the lexical environment of the new execution context.Note that this link between the new execution context and the lexical environment of the function object is called a closure.
Thus, in JavaScript, scope is implemented via lexical environments linked together in a "chain" by outer references. This chain of lexical environments is called the scope chain, and identifier resolution occurs by searching up the chain for a matching identifier.
Find out more.
Javascript 使用作用域链来建立给定函数的作用域。 通常有一个全局作用域,并且定义的每个函数都有其自己的嵌套作用域。 在另一个函数中定义的任何函数都具有链接到外部函数的局部作用域。 定义范围的始终是源代码中的位置。
作用域链中的元素基本上是一个带有指向其父作用域的指针的 Map。
解析变量时,JavaScript 从最内层范围开始向外搜索。
Javascript uses scope chains to establish the scope for a given function. There is typically one global scope, and each function defined has its own nested scope. Any function defined within another function has a local scope which is linked to the outer function. It's always the position in the source that defines the scope.
An element in the scope chain is basically a Map with a pointer to its parent scope.
When resolving a variable, javascript starts at the innermost scope and searches outwards.
全局声明的变量具有全局作用域。 函数内声明的变量的作用域为该函数,以及同名的影子全局变量。
(我确信真正的 JavaScript 程序员能够在其他答案中指出许多微妙之处。特别是我遇到了 此页面随时了解
此
的确切含义。 htm#part4" rel="noreferrer">这个更具介绍性的链接足以让您开始。)Variables declared globally have a global scope. Variables declared within a function are scoped to that function, and shadow global variables of the same name.
(I'm sure there are many subtleties that real JavaScript programmers will be able to point out in other answers. In particular I came across this page about what exactly
this
means at any time. Hopefully this more introductory link is enough to get you started though.)老派 JavaScript
传统上,JavaScript 实际上只有两种类型的作用域:
我不会详细说明这一点,因为已经有很多其他答案解释了这种差异。
现代 JavaScript
最新 JavaScript 规范 现在还允许第三个作用域:
如何创建块作用域变量?
传统上,您像这样创建变量:
块作用域变量是这样创建的:
那么函数作用域和块作用域之间有什么区别?
要理解函数作用域和块作用域之间的区别,请考虑以下代码:
在这里,我们可以看到变量
j
仅在第一个 for 循环中已知,而不是之前和之后。 然而,我们的变量i
在整个函数中是已知的。另外,请考虑块作用域变量在声明之前是未知的,因为它们没有被提升。 您也不允许在同一块内重新声明同一块作用域变量。 这使得块作用域变量比全局或函数作用域变量更不容易出错,全局或函数作用域变量被提升并且在多个声明的情况下不会产生任何错误。
现在使用块作用域变量安全吗?
今天使用是否安全,取决于您的环境:
如果您正在编写服务器端 JavaScript 代码 (Node.js),您可以安全地使用
let
语句。如果您正在编写客户端 JavaScript 代码并使用基于浏览器的转译器(例如 Traceur 或 babel-standalone),您可以安全地使用
let
语句,但是您的代码在性能方面可能并非最佳。如果您正在编写客户端 JavaScript 代码并使用基于 Node 的转译器(例如 traceur shell 脚本 或 Babel ),您可以安全地使用
let
语句。 而且由于您的浏览器只会了解转译的代码,因此性能缺陷应该是有限的。如果您正在编写客户端 JavaScript 代码并且不使用转译器,则需要考虑浏览器支持。
以下是一些根本不支持
let
的浏览器:如何跟踪浏览器支持
了解在您使用时哪些浏览器支持
let
语句的最新概述阅读此答案,请参阅此我可以使用
页面。(*) 全局和功能范围的变量可以在声明之前初始化和使用,因为 JavaScript 变量是 提升。这意味着声明始终位于范围的顶部。
Old school JavaScript
Traditionally, JavaScript really only has two types of scope :
I will not elaborate on this, since there are already many other answers explaining the difference.
Modern JavaScript
The most recent JavaScript specs now also allow a third scope :
How do I create block scope variables?
Traditionally, you create your variables like this :
Block scope variables are created like this :
So what is the difference between functional scope and block scope?
To understand the difference between functional scope and block scope, consider the following code :
Here, we can see that our variable
j
is only known in the first for loop, but not before and after. Yet, our variablei
is known in the entire function.Also, consider that block scoped variables are not known before they are declared because they are not hoisted. You're also not allowed to redeclare the same block scoped variable within the same block. This makes block scoped variables less error prone than globally or functionally scoped variables, which are hoisted and which do not produce any errors in case of multiple declarations.
Is it safe to use block scope variables today?
Whether or not it is safe to use today, depends on your environment :
If you're writing server-side JavaScript code (Node.js), you can safely use the
let
statement.If you're writing client-side JavaScript code and use a browser based transpiler (like Traceur or babel-standalone), you can safely use the
let
statement, however your code is likely to be anything but optimal with respect to performance.If you're writing client-side JavaScript code and use a Node based transpiler (like the traceur shell script or Babel), you can safely use the
let
statement. And because your browser will only know about the transpiled code, performance drawbacks should be limited.If you're writing client-side JavaScript code and don't use a transpiler, you need to consider browser support.
These are some browsers that don't support
let
at all :How to keep track of browser support
For an up-to-date overview of which browsers support the
let
statement at the time of your reading this answer, see thisCan I Use
page.(*) Globally and functionally scoped variables can be initialized and used before they are declared because JavaScript variables are hoisted. This means that declarations are always much to the top of the scope.
下面是一个示例:
您需要研究闭包,以及如何使用它们来创建私有成员。
Here's an example:
You'll want to investigate closures, and how to use them to make private members.
据我了解,关键在于 Javascript 具有函数级作用域,而不是更常见的 C 块作用域。
这是一篇关于该主题的好文章。
The key, as I understand it, is that Javascript has function level scoping vs the more common C block scoping.
Here is a good article on the subject.
在“Javascript 1.7”(Mozilla 对 Javascript 的扩展)中,还可以使用 < 声明块作用域变量代码>let语句:
In "Javascript 1.7" (Mozilla's extension to Javascript) one can also declare block-scope variables with
let
statement:JavaScript 作用域的想法最初由 Brendan Eich 设计,来自 HyperCard 脚本语言HyperTalk。
在这种语言中,显示的方式类似于一叠索引卡。 有一张称为背景的主卡。 它是透明的,可以看到是底卡。 此基础卡上的任何内容都与放置在其顶部的卡共享。 放置在顶部的每张卡片都有自己的内容,这些内容优先于前一张卡片,但如果需要,仍然可以访问先前的卡片。
这正是 JavaScript 作用域系统的设计方式。 它只是有不同的名称。 JavaScript 中的卡片称为执行上下文ECMA< /a>。 这些上下文中的每一个都包含三个主要部分。 变量环境、词法环境和 this 绑定。 回到卡片参考,词法环境包含堆栈中较低位置的先前卡片的所有内容。 当前上下文位于堆栈顶部,其中声明的任何内容都将存储在变量环境中。 在命名冲突的情况下,变量环境将优先。
this 绑定将指向包含对象。 有时,作用域或执行上下文会发生变化,而包含对象不会发生变化,例如在声明的函数中,其中包含对象可能是 window 或构造函数。
这些执行上下文在控制权转移时创建。 当代码开始执行时,控制权就会转移,这主要是通过函数执行来完成的。
这就是技术解释。 在实践中,重要的是要记住,JavaScript
将此应用于本页上的先前示例之一(5.“闭包”),可以跟踪执行上下文的堆栈。 在此示例中,堆栈中有三个上下文。 它们由外部上下文、由 var 6 调用的立即调用函数中的上下文以及 var 6 立即调用函数内部的返回函数中的上下文定义。
i) 外部上下文。 它有一个变量环境 a = 1
ii) IIFE 上下文,它有一个 a = 1 的词法环境,但是 a = 6 的变量环境在堆栈中优先
iii) 返回的函数上下文,它有一个 a = 6 的词法环境,这是调用时警报中引用的值。
The idea of scoping in JavaScript when originally designed by Brendan Eich came from the HyperCard scripting language HyperTalk.
In this language, the displays were done similar to a stack of index cards. There was a master card referred to as the background. It was transparent and can be seen as the bottom card. Any content on this base card was shared with cards placed on top of it. Each card placed on top had its own content which took precedence over the previous card, but still had access to the prior cards if desired.
This is exactly how the JavaScript scoping system is designed. It just has different names. The cards in JavaScript are known as Execution ContextsECMA. Each one of these contexts contains three main parts. A variable environment, a lexical environment, and a this binding. Going back to the cards reference, the lexical environment contains all of the content from prior cards lower in the stack. The current context is at the top of the stack and any content declared there will be stored in the variable environment. The variable environment will take precedence in the case of naming collisions.
The this binding will point to the containing object. Sometimes scopes or execution contexts change without the containing object changing, such as in a declared function where the containing object may be
window
or a constructor function.These execution contexts are created any time control is transferred. Control is transferred when code begins to execute, and this is primarily done from function execution.
So that is the technical explanation. In practice, it is important to remember that in JavaScript
Applying this to one of the previous examples (5. "Closure") on this page, it is possible to follow the stack of execution contexts. In this example there are three contexts in the stack. They are defined by the outer context, the context in the immediately invoked function called by var six, and the context in the returned function inside of var six's immediately invoked function.
i) The outer context. It has a variable environment of a = 1
ii) The IIFE context, it has a lexical environment of a = 1, but a variable environment of a = 6 which takes precedence in the stack
iii) The returned function context, it has a lexical environment of a = 6 and that is the value referenced in the alert when called.
1) 有全局作用域、函数作用域以及 with 和 catch 作用域。 一般来说,变量没有“块”级作用域——with 和 catch 语句将名称添加到它们的块中。
2) 作用域由函数一直嵌套到全局作用域。
3)通过原型链来解析属性。 with 语句将对象属性名称带入 with 块定义的词法范围中。
编辑:ECMAAScript 6 (Harmony) 被指定为支持 let,而且我知道 chrome 允许使用 'harmony' 标志,所以也许它确实支持它。.
Let 将是对块级作用域的支持,但你必须使用关键字来实现它。
编辑:根据本杰明在评论中指出的 with 和 catch 语句,我编辑了这篇文章,并添加了更多内容。 with 和 catch 语句都将变量引入到各自的块中,并且这是一个块作用域。 这些变量是传递给它们的对象的属性的别名。
编辑:澄清示例:
test1 的作用域为 with 块,但别名为 a.test1。 'Var test1' 在上层词法上下文(函数或全局)中创建一个新变量 test1,除非它是 a 的属性 -- 确实如此。
哎呀! 使用“with”时要小心——就像 var 是一个 noop(如果变量已经在函数中定义)一样,对于从对象导入的名称来说,它也是一个 noop! 对已经定义的名称稍作提示将使这更安全。 因此,我个人永远不会使用 with 。
1) There is a global scope, a function scope, and the with and catch scopes. There is no 'block' level scope in general for variable's -- the with and the catch statements add names to their blocks.
2) Scopes are nested by functions all the way to the global scope.
3) Properties are resolved by going through the prototype chain. The with statement brings object property names into the lexical scope defined by the with block.
EDIT: ECMAAScript 6 (Harmony) is spec'ed to support let, and I know chrome allows a 'harmony' flag, so perhaps it does support it..
Let would be a support for block level scoping, but you have to use the keyword to make it happen.
EDIT: Based on Benjamin's pointing out of the with and catch statements in the comments, I've edited the post, and added more. Both the with and the catch statements introduce variables into their respective blocks, and that is a block scope. These variables are aliased to the properties of the objects passed into them.
EDIT: Clarifying example:
test1 is scoped to the with block, but is aliased to a.test1. 'Var test1' creates a new variable test1 in the upper lexical context (function, or global), unless it is a property of a -- which it is.
Yikes! Be careful using 'with' -- just like var is a noop if the variable is already defined in the function, it is also a noop with respect to names imported from the object! A little heads up on the name already being defined would make this much safer. I personally will never use with because of this.