javascript - JavaScript中变量的范围是什么?

标签 javascript function variables scope var

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

最佳答案

TLDR

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

四个范围是:


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


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

总览

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

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

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

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

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

确定JavaScript中scopeidentifier的三个相关因素:


标识符如何声明
声明标识符的地方
无论您处于strict mode还是non-strict mode


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


varletconst
功能参数
捕获块参数
函数声明
命名函数表达式
隐式变量声明
import语句
eval


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


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


声明样式

变种

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

let和const

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

注意:letconstvar are all hoisted。这意味着它们的逻辑定义位置是其包围范围(模块或功能)的顶部。但是,在控制已通过源代码中的声明点之前,无法读取或分配使用letconst声明的变量。过渡期称为时间盲区。



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





功能参数名称

函数参数名称的作用域为函数主体。

函数声明

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

命名函数表达式

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

隐式变量声明

在非严格模式下,隐式变量声明具有全局范围。在严格模式下,这些是不允许的。

评估

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

例子

以下内容将引发ReferenceError,因为名称xyz在函数f之外没有任何意义。



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





以下内容将为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五次,然后在循环外为5第六次打印console.log



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





以下打印undefined,因为x是块作用域的。回调是异步进行的。 let变量的新行为意味着每个匿名函数都关闭了一个名为x的变量(与var不同),因此将打印从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





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



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


以下将在全局对象上声明一个属性,因为在全局上下文中使用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





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



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 // implicit variable declaration (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





它是如何工作的

范围定义为代码的lexical区域,在该区域上标识符是有效的。

在JavaScript中,每个功能对象都有一个隐藏的[[Environment]]引用,该引用是对在其中创建它的lexical environment(堆栈框架)的execution context的引用。

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

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

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

找出more

关于javascript - JavaScript中变量的范围是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39824686/

相关文章:

javascript - 如何引用 aurelia 中的重复父索引?

javascript - jquery过滤器 "a"带有条件的标签

javascript - Karma + Webpack 测试意外 token '/'

c++ - 返回语句与接受指针写入

javascript - 返回带有变量名称的值

javascript - Redux:使用 compose() 或不使用它的 applyMiddleware 之间的区别?

python - 撤消 Python 内置的覆盖

python - 从字符串中删除出现在前一个字符串中的字母

javascript - 变量作用域和 Var

Javascript:变量作为 While 条件