JavaScript 中变量的作用域是什么? [英] What is the scope of variables in JavaScript?

查看:35
本文介绍了JavaScript 中变量的作用域是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

解决方案

TLDR

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

四个范围是:

  1. 全局 - 一切可见
  2. 函数 - 在函数(及其子函数和块)中可见
  3. 块 - 在块(及其子块)内可见
  4. 模块 - 在模块内可见

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

概述

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

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

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

这些链接的词法环境形成了一个作用域链".标识符解析是沿着这条链搜索匹配标识符的过程.

标识符解析只发生在一个方向:向外.以这种方式,外部词汇环境无法看到"外部词汇环境.进入内部词汇环境.

决定范围的三个相关因素是标识符:

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

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

  1. varletconst
  2. 函数参数
  3. 捕获块参数
  4. 函数声明
  5. 命名函数表达式
  6. 在全局对象上隐式定义的属性(即,在非严格模式下遗漏了 var)
  7. import 语句
  8. eval

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

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

声明样式

变量

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

let 和 const

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

注意:letconstvar 都被吊起来了.这意味着它们定义的逻辑位置是它们的封闭范围(块或函数)的顶部.但是,使用 letconst 声明的变量在控制通过源代码中的声明点之前无法读取或分配.过渡期称为时间死区.

function f() {函数 g() {控制台.log(x)}让 x = 1G()}f()//1 因为即使使用 `let` 声明 x 也被提升了!

函数参数名称

函数参数名称的作用域是函数体.请注意,这有点复杂.在参数列表上声明为默认参数的函数,而不是函数体.

函数声明

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

命名函数表达式

命名函数表达式的作用域是自身(例如,为了递归).

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

在非严格模式下,全局对象上隐式定义的属性具有全局作用域,因为全局对象位于作用域链的顶部.在严格模式下,这些是不允许的.

评估

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

示例

以下将抛出一个ReferenceError,因为名称xyz在函数f之外没有任何意义.

function f() {无功x = 1让 y = 1常量 z = 1}console.log(typeof x)//未定义(因为 var 有函数作用域!)console.log(typeof y)//未定义(因为函数体是块)console.log(typeof z)//未定义(因为函数体是块)

下面会为yz抛出一个ReferenceError,但对于x不会,因为x的可见性code> 不受块的约束.定义控制结构体的块,如 ifforwhile,行为类似.

{无功x = 1让 y = 1常量 z = 1}console.log(x)//1console.log(typeof y)//未定义,因为 `y` 有块作用域console.log(typeof z)//未定义,因为 `z` 具有块作用域

在下面,x 在循环外是可见的,因为 var 有函数作用域:

for(var x = 0; x <5; ++x) {}console.log(x)//5(注意这是在循环之外!)

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

下面将 5 打印五次,然后在循环外为 console.log 打印第六次 5:>

for(var x = 0; x <5; ++x) {setTimeout(() => console.log(x))//关闭`x`,它在逻辑上位于封闭作用域的顶部,循环上方}console.log(x)//注意:循环外可见

以下打印 undefined 因为 x 是块范围的.回调是一一异步运行的.let 变量的新行为意味着每个匿名函数关闭一个名为 x 的不同变量(与 var 不同),所以打印整数 04.:

for(let x = 0; x <5; ++x) {setTimeout(() => console.log(x))//`let` 声明在每次迭代的基础上重新声明,因此闭包捕获不同的变量}console.log(typeof x)//未定义

以下不会抛出 ReferenceError 因为 x 的可见性不受块的约束;但是,它会打印 undefined 因为变量尚未初始化(因为 if 语句).

if(false) {无功x = 1}console.log(x)//这里,`x` 已经声明,但没有初始化

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

for(let x = 0; x <10; ++x) {}console.log(typeof x)//未定义,因为 `x` 是块作用域

以下将抛出一个ReferenceError,因为x 的可见性受到块的约束:

if(false) {让 x = 1}console.log(typeof x)//未定义,因为 `x` 是块作用域

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

//module1.js无功 x = 0导出函数 f() {}//module2.js从'module1.js'导入fconsole.log(x)//抛出 ReferenceError

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

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

全局上下文中的

letconst不给全局对象添加属性,但仍然具有全局作用域:

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

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

function f(x) {}console.log(typeof x)//未定义,因为 `x` 的作用域是函数

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

try {} catch(e) {}console.log(typeof e)//未定义,因为 `e` 的作用域是 catch 块

命名函数表达式的范围仅限于表达式本身:

(function foo() { console.log(foo) })()console.log(typeof foo)//未定义,因为 `foo` 的作用域是它自己的表达式

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

x = 1//在全局对象上隐式定义的属性(没有var"!)console.log(x)//1console.log(window.hasOwnProperty('x'))//true

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

'use strict'{函数 foo() {}}console.log(typeof foo)//未定义,因为 `foo` 是块作用域

它是如何工作的

范围被定义为标识符有效的词法代码区域.

在 JavaScript 中,每个函数对象都有一个隐藏的 [[Environment]] 引用,它是对 词法环境 执行上下文(堆栈帧),在其中创建.

当你调用一个函数时,隐藏的[[Call]]方法被调用.该方法创建一个新的执行上下文并在新的执行上下文和函数对象的词法环境之间建立链接.它通过将函数对象上的 [[Environment]] 值复制到 外部引用 新执行上下文的词法环境上的字段.

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

因此,在 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?

解决方案

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.

这篇关于JavaScript 中变量的作用域是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆