JavaScript 中变量的作用域是什么?

发布于 2024-07-13 05:03:01 字数 85 浏览 4 评论 0 原文

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

What is the scope of variables in javascript? Do they have the same scope inside as opposed to outside a function? Or does it even matter? Also, where are the variables stored if they are defined globally?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(27

薄情伤 2024-07-20 05:03:02

内联处理程序

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

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

on* 属性可以引用必须 可以是:

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

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

window.addEventListener('DOMContentLoaded', () => {
  function foo() {
    console.log('foo running');
  }
});
<button onclick="foo()">click</button>

document 的属性和处理程序所附加的元素的属性也可以在内联处理程序中作为独立变量进行引用,因为内联处理程序被调用 内部两个 with,一个用于文档,一个用于元素。 这些处理程序内的变量作用域链极其不直观,并且工作的事件处理程序可能需要函数是全局的(并且应该避免不必要的全局污染应该避免)。

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

function foo() {
  console.log('foo running');
}
document.querySelector('.my-button').addEventListener('click', foo);
<button class="my-button">click</button>

模块 (

另一方面,与正常的

<script>
const foo = 'foo';
</script>
<script>
console.log(foo);
</script>

但 ES6 模块的顶层不是全局的。 在 ES6 模块顶部声明的变量仅在该模块内部可见,除非该变量被显式导出,或者除非它被分配给全局对象的属性。

<script type="module">
const foo = 'foo';
</script>
<script>
// Can't access foo here, because the other script is a module
console.log(typeof foo);
</script>

ES6 模块的顶层类似于普通

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

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

The scope of the variables that an on* attribute can reference must be either:

  • global (working inline handlers almost always reference global variables)
  • a property of the document (eg, querySelector as a standalone variable will point to document.querySelector; rare)
  • a property of the element the handler is attached to (like above; 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:

window.addEventListener('DOMContentLoaded', () => {
  function foo() {
    console.log('foo running');
  }
});
<button onclick="foo()">click</button>

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 two with blocks, one for the document, 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.

function foo() {
  console.log('foo running');
}
document.querySelector('.my-button').addEventListener('click', foo);
<button class="my-button">click</button>

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:

<script>
const foo = 'foo';
</script>
<script>
console.log(foo);
</script>

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 exported, or unless it's assigned to a property of the global object.

<script type="module">
const foo = 'foo';
</script>
<script>
// Can't access foo here, because the other script is a module
console.log(typeof foo);
</script>

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.

千柳 2024-07-20 05:03:02

我发现许多 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.

玻璃人 2024-07-20 05:03:02

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

  1. 全局作用域:全局只不过是窗口级作用域。在这里,变量存在于整个应用程序中。
  2. 函数作用域:在函数内使用 var 关键字声明的变量具有函数作用域。

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

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

作用域链 -->

  1. 窗口级别 - aouter 函数位于作用域链的顶层。
  2. 当外部函数调用一个新的变量作用域对象(并包含在作用域链中)时,其中添加了变量 b 。

现在,当变量 a 需要时,它首先搜索最近的变量范围,如果变量不存在,则它移动到变量范围链的下一个对象。在本例中是窗口级别。

JavaScript have only two type of scope :

  1. Global Scope : Global is nothing but a window level scope.Here, variable present throughout the application.
  2. Functional Scope : Variable declared within a function with 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.

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

Scope chain -->

  1. Window level - a and outer function are at top level in scope chain.
  2. when outer function called a new variable scope object(and included in scope chain) added with variable b 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.

向日葵 2024-07-20 05:03:02

运行代码。 希望这能提供有关范围界定的想法

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

run the code. hope this will give an idea about scoping

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);
云淡月浅 2024-07-20 05:03:02

全局范围:

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

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

查看这篇文章深入了解范围

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.

//global variable
var a = 2;

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

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

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    

Check this article for in-depth understanding of scope

情深缘浅 2024-07-20 05:03:02

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

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

这几乎是您在 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);

这可能看起来违反直觉,但它使得从命令式语言设计者的角度来看。

There are ALMOST only two types of JavaScript scopes:

  • the scope of each var declaration is associated with the most immediately enclosing function
  • if there is no enclosing function for a var declaration, it is global scope

So, any blocks other than functions do not create a new scope. That explains why for-loops overwrite outer scoped variables:

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

Using functions instead:

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

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:

  • var declarations are hoisted to the top of the scope. This means no matter where the var declaration happens, to the compiler it is as if the var itself happens at the top
  • multiple var declarations within the same scope are combined

So this code:

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

is equivalent to:

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

This may seem counter intuitive, but it makes sense from the perspective of a imperative language designer.

你怎么这么可爱啊 2024-07-20 05:03:02

只是为了添加其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则来确定当前执行的代码如何访问这些标识符。 此查找可能是为了分配给变量(LHS(左侧)引用),也可能是为了检索其值(RHS(右侧)引用)。 这些查找是 JavaScript 引擎在编译和执行代码时在内部执行的操作。

因此,从这个角度来看,我认为我在 Kyle Simpson 的《作用域和闭包》电子书中找到的一张图片会有所帮助:

image

引用他的电子书:

这座建筑代表了我们程序的嵌套范围规则集。 首先
建筑物的楼层代表您当前执行的范围,
无论你在哪。 大楼的顶层是全局范围。
您可以通过查看当前楼层来解析 LHS 和 RHS 参考,
如果没找到就坐电梯到下一层
看看那里,然后看看下一个,依此类推。 一旦你到达顶楼
(全球范围),你要么找到你正在寻找的东西,要么你
不。 但无论如何你都必须停下来。

值得一提的是,“一旦找到第一个匹配项,范围查找就会停止”。

“范围级别”的概念解释了为什么“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:

image

Quoting from his ebook:

The building represents our program’s nested scope ruleset. The first
floor of the building represents your currently executing scope,
wherever you are. The top level of the building is the global scope.
You resolve LHS and RHS references by looking on your current floor,
and if you don’t find it, taking the elevator to the next floor,
looking there, then the next, and so on. Once you get to the top floor
(the global scope), you either find what you’re looking for, or you
don’t. But you have to stop regardless.

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

乜一 2024-07-20 05:03:02

现代 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 be const.

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, use let.

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.

三人与歌 2024-07-20 05:03:02

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

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

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.

var a = new Date();
function f1(b)
{
    b.setDate(b.getDate()+1);
    alert(b.getDate());
}
f1(a);
alert(a.getDate());
生寂 2024-07-20 05:03:02

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

There are only function scopes in JS. Not block scopes!
You can see what is hoisting too.

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);
蓝色星空 2024-07-20 05:03:02

我的理解是有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

绿萝 2024-07-20 05:03:02

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

当我们尝试将变量 foobarfoobar 记录到控制台时,会发生以下情况:

  1. 我们尝试将 foo 记录到在控制台中,foo 可以在函数 innerFunc 本身中找到。 因此,foo 的值被解析为字符串innerFunc
  2. 我们尝试将 bar 记录到控制台,但在函数 innerFunc 本身内部找不到 bar。 因此,我们需要攀爬作用域链。 我们首先查看定义了函数 innerFunc 的外部函数。 这是函数outerFunc。 在outerFunc的范围内,我们可以找到变量bar,它保存字符串“outerFunc”。
  3. 在innerFunc 中找不到foobar。 。 因此,我们需要爬上作用域链到innerFunc作用域。 这里也找不到,我们再爬一层到全局作用域(即最外层作用域)。 我们在这里找到变量 foobar,它保存字符串“global”。 如果在爬升作用域链后仍未找到变量,JS 引擎将抛出 referenceError

ES6 (ES 2015) 及更早版本:

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

  • let:创建块作用域变量
  • const:创建块作用域变量,必须初始化且不能重新赋值

var 之间最大的区别let/constvar 是函数作用域,而 let/const< /code> 是块作用域的。 下面是一个例子来说明这一点:

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 块之外访问该变量。

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:

  1. When a function look to resolve a variable value it first looks at its own scope. This is the function body, i.e. everything between curly brackets {} (except for variables inside other functions which are in this scope).
  2. If it cannot find the variable inside the function body it will climb up to the chain and look at the variable scope in the function in where the function was defined. This is what is meant with lexical scope, we can see in the code where this function was defined and thus can determine the scope chain by merely looking at the code.

Example:

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

What happens when we are trying to log the variables foo, bar, and foobar to the console is the following:

  1. We try to log foo to the console, foo can be found inside the function innerFunc itself. Therefore, the value of foo is resolved to the string innerFunc.
  2. We try to log bar to the console, bar cannot be found inside the function innerFunc itself. Therefore, we need to climb the scope chain. We first look in the outer function in which the function innerFunc was defined. This is the function outerFunc. In the scope of outerFunc we can find the variable bar, which holds the string 'outerFunc'.
  3. foobar cannot be found in innerFunc. . Therefore, we need to climb the scope chain to the innerFunc scope. It also cannot be found here, we climb another level to the global scope (i.e. the outermost scope). We find the variable foobar here which holds the string 'global'. If it wouldnot have found the variable after climbing the scope chain the JS engine would throw a referenceError.

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 variable
  • const: creates a block scoped variable which has to be initialized and cannot be reassigned

The biggest difference between var and let/const is that var is function scoped whereas let/const are block scoped. Here is an example to illustrate this:

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

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.

ぺ禁宫浮华殁 2024-07-20 05:03:02

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

In JavaScript there are two types of scope:

  • Local scope
  • Global scope

The Below function has a local scope variable carName. And this variable is not accessible from outside of the function.

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

The Below Class has a Global scope variable carName. And this variable is accessible from everywhere in the class.

class {

    var carName = " Volvo";

    // code here can use carName

    function myFunction() {
        alert(carName);
        // code here can use carName 
    }
}
想你的星星会说话 2024-07-20 05:03:02

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

范围收集并维护所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则来确定当前执行的访问方式代码。

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

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

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.

  • If a variable cannot be found in the immediate scope, Engine consults the next outer containing scope, continuing until is found or until the outermost (a.k.a., global) scope has been reached.
  • Is the set of rules that determines where and how a variable (identifier) can be looked up. This look-up may be for the purposes of assigning to the variable, which is an LHS (left-hand-side) reference, or it may be for the purposes of retrieving its value, which is an RHS (righthand-side) reference.
  • LHS references result from assignment operations. Scope-related assignments can occur either with the = operator or by passing arguments to (assign to) function parameters.
  • The JavaScript engine first compiles code before it executes, and in so doing, it splits up statements like var a = 2; into two separate steps: 1st. First, var a to declare it in that scope. This is performed at the beginning, before code execution. 2nd. Later, a = 2 to look up the variable (LHS reference) and assign to it if found.
  • Both LHS and RHS reference look-ups start at the currently executing scope, and if need be (that is, they don’t find what they’re looking for there), they work their way up the nested scope, one scope (floor) at a time, looking for the identifier, until they get to the global (top floor) and stop, and either find it, or don’t. Unfulfilled RHS references result in ReferenceError being thrown. Unfulfilled LHS references result in an automatic, implicitly created global of that name (if not in Strict Mode), or a ReferenceError (if in Strict Mode).
  • scope consists of a series of “bubbles” that each act as a container or bucket, in which identifiers (variables, functions) are declared. These bubbles nest neatly inside each other, and this nesting is defined at author time.
世界如花海般美丽 2024-07-20 05:03:02

在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.
}

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

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.
}
说谎友 2024-07-20 05:03:02

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

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.

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
幸福还没到 2024-07-20 05:03:02
(function foo() { console.log(foo) })();
console.log(typeof foo); // undefined, because `foo` is scoped to its own expression

//but, like this
(function foo() {
    console.log('1:', foo) // function foo
    foo = 100
    console.log('2:', foo) // function foo, is not 100, why?
})()

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

//but, like this
(function foo() {
    console.log('1:', foo) // function foo
    foo = 100
    console.log('2:', foo) // function foo, is not 100, why?
})()

半夏半凉 2024-07-20 05:03:02

JavaScript 中有两种类型的作用域。

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

    var carName = "宝马"; 
    
      // 这里的代码可以使用carName 
    
      函数 myFunction() { 
           // 这里的代码可以使用carName  
      } 
      
  2. 函数作用域或局部作用域:在此作用域中声明的变量只能在其自己的函数中使用。 例如:

    //这里的代码不能使用carName 
      函数 myFunction() { 
         var 汽车名称 = "宝马"; 
         // 这里的代码可以使用carName 
      } 
      

There are two types of scopes in JavaScript.

  1. Global scope: variable which is announced in global scope can be used anywhere in the program very smoothly. For example:

    var carName = " BMW";
    
    // code here can use carName
    
    function myFunction() {
         // code here can use carName 
    }
    
  2. Functional scope or Local scope: variable declared in this scope can be used in its own function only. For example:

    // code here can not use carName
    function myFunction() {
       var carName = "BMW";
       // code here can use carName
    }
    
薯片软お妹 2024-07-20 05:03:01

TLDR

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

这四个范围是:

  1. 全局 - 对所有内容可见
  2. 功能 - 在函数(及其子函数和块)内可见
  3. 块 - 在块(及其子块)内可见
  4. 模块 - 在模块内可见

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

概述

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

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

作用域由词法环境的链接嵌套构成,嵌套中的每个级别对应于祖先执行上下文的词法环境。

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

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

决定范围时需要考虑三个相关因素: //www.ecma-international.org/ecma-262/10.0/index.html#sec-names-and-keywords" rel="noreferrer">JavaScript 中的标识符:

  1. 如何声明标识符
  2. 标识符在哪里已声明
  3. 无论您处于严格模式还是非严格模式

声明标识符的一些方式:

  1. var letconst
  2. 函数参数
  3. Catch 块参数
  4. 函数声明
  5. 命名函数表达式
  6. 全局对象上隐式定义的属性(即缺少 var code> 非严格模式)
  7. import 语句
  8. eval

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

  1. 全局上下文
  2. 函数体
  3. 普通块
  4. 控制结构的顶部(例如,循环、 if、 while 等)
  5. 控制结构体
  6. 模块

声明样式

var

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

let 和 const

使用 letconst 声明的标识符具有块作用域,除非它们直接在全局上下文中声明,在这种情况下它们具有全球范围。

注意: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

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)

以下代码将为 yz 抛出 ReferenceError,但不会为 x 抛出 ReferenceError,因为 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!)

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

以下代码打印 5 五次,然后为循环外的 console.log 打印 5 第六次:

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 不同),因此打印整数 04。:

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

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

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

以下代码将抛出 ReferenceError,因为 x 的可见性受到块的限制:

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

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

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

全局上下文中的 letconst 不会向全局对象添加属性,但仍然具有全局作用域:

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

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

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

Catch 块参数的作用域为 catch 块主体:

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 中,作用域是通过外部引用以“链”形式链接在一起的词法环境来实现的。 这个词法环境链称为作用域链,标识符解析通过 在链上搜索以查找匹配的标识符。

了解更多信息

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:

  1. Global - visible by everything
  2. Function - visible within a function (and its sub-functions and blocks)
  3. Block - visible within a block (and its sub-blocks)
  4. Module - visible within a module

Outside of the special cases of global and module scope, variables are declared using var (function scope), let (block scope), and const (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:

  1. How an identifier was declared
  2. Where an identifier was declared
  3. Whether you are in strict mode or non-strict mode

Some of the ways identifiers can be declared:

  1. var, let and const
  2. Function parameters
  3. Catch block parameter
  4. Function declarations
  5. Named function expressions
  6. Implicitly defined properties on the global object (i.e., missing out var in non-strict mode)
  7. import statements
  8. eval

Some of the locations identifiers can be declared:

  1. Global context
  2. Function body
  3. Ordinary block
  4. The top of a control structure (e.g., loop, if, while, etc.)
  5. Control structure body
  6. Modules

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 in eval functions.

let and const

Identifiers declared using let and const have block scope, apart from when they are declared directly in the global context, in which case they have global scope.

Note: let, const and var are all hoisted. This means that their logical position of definition is the top of their enclosing scope (block or function). However, variables declared using let and const 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 f() {
    function g() {
        console.log(x)
    }
    let x = 1
    g()
}
f() // 1 because x is hoisted even though declared with `let`!

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 using var will be placed in the current scope, or, if eval is used indirectly, as properties on the global object.

Examples

The following will throw a ReferenceError because the namesx, y, and z have no meaning outside of the function 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)

The following will throw a ReferenceError for y and z, but not for x, because the visibility of x is not constrained by the block. Blocks that define the bodies of control structures like if, for, and while, behave similarly.

{
    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

In the following, x is visible outside of the loop because var has function scope:

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

...because of this behavior, you need to be careful about closing over variables declared using var in loops. There is only one instance of variable x declared here, and it sits logically outside of the loop.

The following prints 5, five times, and then prints 5 a sixth time for the console.log outside the loop:

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

The following prints undefined because x is block-scoped. The callbacks are run one by one asynchronously. New behavior for let variables means that each anonymous function closed over a different variable named x (unlike it would have done with var), and so integers 0 through 4 are printed.:

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

The following will NOT throw a ReferenceError because the visibility of x is not constrained by the block; it will, however, print undefined because the variable has not been initialised (because of the if statement).

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

A variable declared at the top of a for loop using let is scoped to the body of the loop:

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

The following will throw a ReferenceError because the visibility of x is constrained by the block:

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

Variables declared using var, let or const are all scoped to modules:

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

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:

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

let and const in the global context do not add properties to the global object, but still have global scope:

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

Function parameters can be considered to be declared in the function body:

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

Catch block parameters are scoped to the catch-block body:

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

Named function expressions are scoped only to the expression itself:

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

In non-strict mode, implicitly defined properties on the global object are globally scoped. In strict mode, you get an error.

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

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

In non-strict mode, function declarations have function scope. In strict mode, they have block scope.

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

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.

萌辣 2024-07-20 05:03:01

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.

深居我梦 2024-07-20 05:03:01

全局声明的变量具有全局作用域。 函数内声明的变量的作用域为该函数,以及同名的影子全局变量。

(我确信真正的 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.)

jJeQQOZ5 2024-07-20 05:03:01

老派 JavaScript

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

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

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


现代 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 babel-standalone),您可以安全地使用 let 语句,但是您的代码在性能方面可能并非最佳。

  • 如果您正在编写客户端 JavaScript 代码并使用基于 Node 的转译器(例如 traceur shell 脚本Babel ),您可以安全地使用 let 语句。 而且由于您的浏览器只会了解转译的代码,因此性能缺陷应该是有限的。

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

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

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

在此处输入图像描述


如何跟踪浏览器支持

了解在您使用时哪些浏览器支持 let 语句的最新概述阅读此答案,请参阅我可以使用页面


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

Old school JavaScript

Traditionally, JavaScript really only has two types of scope :

  1. Global Scope : Variables are known throughout the application, from the start of the application (*)
  2. Functional Scope : Variables are known within the function they are declared in, from the start of the function (*)

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 :

  1. Block Scope : Identifiers are "known" from the top of the scope they are declared within, but they cannot be assigned to or dereferenced (read) until after the line of their declaration. This interim period is called the "temporal dead zone."

How do I create block scope variables?

Traditionally, you create your variables like this :

var myVariable = "Some text";

Block scope variables are created like this :

let myVariable = "Some text";

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 :

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

Here, we can see that our variable j is only known in the first for loop, but not before and after. Yet, our variable i 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 :

    • Internet explorer 10 and below
    • Firefox 43 and below
    • Safari 9 and below
    • Android browser 4 and below
    • Opera 27 and below
    • Chome 40 and below
    • ANY version of Opera Mini & Blackberry Browser

enter image description here


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 this Can 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.

樱桃奶球 2024-07-20 05:03:01

下面是一个示例:

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

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

Here's an example:

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

You'll want to investigate closures, and how to use them to make private members.

动听の歌 2024-07-20 05:03:01

据我了解,关键在于 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.

痕至 2024-07-20 05:03:01

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

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

In "Javascript 1.7" (Mozilla's extension to Javascript) one can also declare block-scope variables with let statement:

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4
[旋木] 2024-07-20 05:03:01

JavaScript 作用域的想法最初由 Brendan Eich 设计,来自 HyperCard 脚本语言HyperTalk

在这种语言中,显示的方式类似于一叠索引卡。 有一张称为背景的主卡。 它是透明的,可以看到是底卡。 此基础卡上的任何内容都与放置在其顶部的卡共享。 放置在顶部的每张卡片都有自己的内容,这些内容优先于前一张卡片,但如果需要,仍然可以访问先前的卡片。

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

this 绑定将指向包含对象。 有时,作用域或执行上下文会发生变化,而包含对象不会发生变化,例如在声明的函数中,其中包含对象可能是 window 或构造函数。

这些执行上下文在控制权转移时创建。 当代码开始执行时,控制权就会转移,这主要是通过函数执行来完成的。

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

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

将此应用于本页上的先前示例之一(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

  • Scopes are technically "Execution Contexts"
  • Contexts form a stack of environments where variables are stored
  • The top of the stack takes precedence (the bottom being the global context)
  • Each function creates an execution context (but not always a new this binding)

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.

enter image description here

夏九 2024-07-20 05:03:01

1) 有全局作用域、函数作用域以及 with 和 catch 作用域。 一般来说,变量没有“块”级作用域——with 和 catch 语句将名称添加到它们的块中。

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

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

编辑:ECMAAScript 6 (Harmony) 被指定为支持 let,而且我知道 chrome 允许使用 'harmony' 标志,所以也许它确实支持它。.

Let 将是对块级作用域的支持,但你必须使用关键字来实现它。

编辑:根据本杰明在评论中指出的 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,除非它是 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.

 //chrome (v8)

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文