当前位置: 首页 > 面试题库 >

JavaScript中变量的范围是什么?

唐彬炳
2023-03-14
问题内容

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


问题答案:

TLDR

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

四个范围是:

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

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

总览

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

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

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

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

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

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

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

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

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

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

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

声明样式

变种

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

let和const

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

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

function f() {

    function g() {

        console.log(x)

    }

    let x = 1

    g()

}

f() // 1 because x is hoisted even though declared with `let`!

功能参数名称

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

函数声明

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

命名函数表达式

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

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

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

评估

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

例子

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

function f() {

    var x = 1

    let y = 1

    const z = 1

}

console.log(typeof x) // undefined (because var has function scope!)

console.log(typeof y) // undefined (because the body of the function is a block)

console.log(typeof z) // undefined (because the body of the function is a block)

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

{

    var x = 1

    let y = 1

    const z = 1

}

console.log(x) // 1

console.log(typeof y) // undefined because `y` has block scope

console.log(typeof z) // undefined because `z` has block scope

在下面,x由于var具有函数作用域,因此在循环外部可见:

for(var x = 0; x < 5; ++x) {}

console.log(x) // 5 (note this is outside the loop!)

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

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

for(var x = 0; x < 5; ++x) {

    setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop

}

console.log(x) // note: visible outside the loop

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

for(let x = 0; x < 5; ++x) {

    setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables

}

console.log(typeof x) // undefined

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

if(false) {

    var x = 1

}

console.log(x) // here, `x` has been declared, but not initialised

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

for(let x = 0; x < 10; ++x) {}

console.log(typeof x) // undefined, because `x` is block-scoped

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

if(false) {

    let x = 1

}

console.log(typeof x) // undefined, because `x` is block-scoped

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

// module1.js

var x = 0
export function f() {}

//module2.js

import f from 'module1.js'

console.log(x) // throws ReferenceError

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

var x = 1

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

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

let x = 1

console.log(window.hasOwnProperty('x')) // false

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

function f(x) {}

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

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

try {} catch(e) {}

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

命名函数表达式仅被表达到表达式本身:

(function foo() { console.log(foo) })()

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

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

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



console.log(x) // 1

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

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

'use strict'

{

    function foo() {}

}

console.log(typeof foo) // undefined, because `foo` is block-scoped

它是如何工作的

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

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

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

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

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



 类似资料:
  • 问题内容: 我想通过使用以下代码来获取全局变量中的数据: 但是问题是我只是在d3.json函数中定义了数据变量,但是没有定义。我该如何解决这个问题? 谢谢 问题答案: 由于d3请求(如)是异步的,因此最佳做法是将所有依赖于外部请求的代码包装在请求回调中,以确保该代码在执行之前可以访问数据。来自D3 docs :“异步加载数据时,依赖于已加载数据的代码通常应存在于回调函数中。” 因此,一种选择是将所

  • 问题内容: 我正在使用dgrid,并且尝试在外部设置dataStore。当页面加载时,我打电话来创建网格。在网格加载时,null为null。执行查询时,将设置。 没有错误,但是网格仍然是空的。正在使用数据更新aliasStore,但是即使刷新了网格也没有将其反映在网格上。查询后如何获取反映在网格中的数据? Javascript对象 设置数据存储数据 问题答案: 您正在将“ this.Store”设

  • 本章介绍当模板在访问变量时发生了什么事情,还有变量是如何存储的。 当调用 Template.process 方法时,它会在方法内部创建一个 Environment 对象,在 process 返回之前一直使用。 该对象存储模板执行时的运行状态信息。除了这些,它还存储由模板中指令,如 assign, macro, local 或 global 创建的变量。 它不会尝试修改传递给 process 的数据

  • 问题内容: 我知道变量作用域由块的开始和块的结尾包围。如果在块内声明了相同的变量,则会发生编译错误。但是,请看以下示例。 在这里,可以在方法中重新声明,尽管它已经在类中声明了。但是在块中,无法重新声明。 为什么类范围变量的重新声明不产生错误,而方法范围变量的重新声明却产生错误? 问题答案: 这是因为不是变量,而是实例字段。允许局部变量与字段具有相同的名称。为了区分变量和具有相同名称的字段,我们在实

  • 问题内容: 我知道变量作用域由块的开始和块的结尾包围。如果在块内声明了相同的变量,则会发生编译错误。但是,请看以下示例。 在这里,可以在方法中重新声明,尽管它已经在类中声明了。但是在块中,无法重新声明。 为什么类范围变量的重新声明不产生错误,而方法范围变量的重新声明却产生错误? 问题答案: 这是因为不是变量,而是实例字段。允许局部变量与字段具有相同的名称。为了区分变量和具有相同名称的字段,我们在实

  • 问题内容: 当我在node.js模块中执行此操作时: 去哪儿了?我的意思是:在浏览器中(如果未在函数中执行或以其他方式执行) 如果我执行此操作: 然后可以在中找到它,但这不是我想要的。 问题答案: 与浏览器不同,浏览器默认情况下将变量分配给全局空间(即窗口),而在Node中, 除非您 将变量 明确 分配给module.exports, 否则 变量的作用域为模块(文件)。 实际上,当您运行或文件中的