javascript中变量的范围是什么?它们在函数内部的作用域与在函数外部的作用域相同吗?或者这有关系吗?此外,如果变量是全局定义的,那么变量存储在哪里?
当前回答
ES5及更早版本:
Javascript中的变量最初(在ES6之前)是函数范围的。词汇范围这一术语意味着您可以通过“查看”代码来查看变量的范围。
用var关键字声明的每个变量都在函数的作用域内。但是,如果在该函数中声明了其他函数,则这些函数将可以访问外部函数的变量。这称为作用域链。其工作方式如下:
当一个函数试图解析一个变量值时,它首先查看自己的范围。这是函数体,即花括号{}之间的所有内容(除了在此范围内的其他函数内的变量)。如果它在函数体中找不到变量,它将爬上链,查看函数中定义函数的变量范围。这就是词法作用域的含义,我们可以在代码中看到这个函数的定义,因此可以通过查看代码来确定作用域链。
例子:
//全局范围var foo=“全局”;var bar=“全局”;var foobar=“全局”;函数outerFunc(){//outerFunc范围var foo='outerFunc';var foobar='outerFunc';innerFunc();函数innerFunc(){//innerFunc范围var foo='innerFunc';console.log(foo);console.log(bar);console.log(foobar);}}outerFunc();
当我们试图将变量foo、bar和foobar记录到控制台时,会发生以下情况:
我们尝试将foo记录到控制台,foo可以在函数innerFunc本身中找到。因此,foo的值被解析为字符串innerFunc。我们试图将bar记录到控制台,但在函数innerFunc本身中找不到bar。因此,我们需要攀登范围链。我们首先查看定义了函数innerFunc的外部函数。这是outerFunc函数。在outerFunc的作用域中,我们可以找到变量栏,其中包含字符串“outerFun”。在innerFunc中找不到foobar。因此,我们需要将作用域链升级到innerFunc作用域。在这里也找不到它,我们爬到了另一个层次的全局范围(即最外面的范围)。我们在这里找到了保存字符串“global”的变量foobar。如果在攀爬范围链之后没有找到变量,JS引擎将抛出referenceError。
ES6(ES 2015)及以上版本:
词汇范围和范围的概念仍然适用于ES6。然而,引入了一种声明变量的新方法。具体如下:
let:创建块范围变量const:创建一个必须初始化且不能重新分配的块范围变量
var和let/cont之间最大的区别是var是函数范围的,而let/cont是块范围的。下面是一个例子来说明这一点:
let letVar=“全局”;var varVar=“全局”;函数foo(){if(真){//用let声明的这个变量的作用域是if块,块作用域设letVar=5;//用let声明的这个变量的作用域是函数块,函数作用域var varVar=10;}console.log(letVar);console.log(varVar);}foo();
在上面的示例中,letVar记录全局值,因为用let声明的变量是块范围的。它们不再存在于各自的块之外,因此无法在if块之外访问变量。
其他回答
TLDR
JavaScript具有词法(也称为静态)作用域和闭包。这意味着您可以通过查看源代码来确定标识符的范围。
这四个范围是:
全局-所有内容都可见函数-在函数(及其子函数和块)中可见块-在块(及其子块)中可见模块-在模块内可见
在全局和模块作用域的特殊情况之外,变量使用var(函数作用域)、let(块作用域)和const(块范围)声明。大多数其他形式的标识符声明在严格模式下具有块范围。
概述
作用域是标识符有效的代码库区域。
词汇环境是标识符名称和与其关联的值之间的映射。
作用域由词法环境的链接嵌套构成,嵌套中的每一层都对应于祖先执行上下文的词法环境。
这些关联的词汇环境形成了一个范围“链”。标识符解析是沿着该链搜索匹配标识符的过程。
标识符解析只发生在一个方向:向外。这样,外部词汇环境就不能“看到”内部词汇环境。
决定JavaScript中标识符的范围有三个相关因素:
如何声明标识符声明标识符的位置无论您处于严格模式还是非严格模式
可以声明标识符的一些方式:
var、let和const功能参数Catch块参数函数声明命名函数表达式全局对象上隐式定义的财产(即,在非限定模式中缺少var)导入语句评估
可以声明一些位置标识符:
全球背景功能体普通砌块控制结构的顶部(例如,循环、if、while等)控制结构体模块
声明样式
var
除了直接在全局上下文中声明外,使用var声明的标识符具有函数范围,在这种情况下,它们被添加为全局对象上的财产,并具有全局范围。在eval函数中使用它们有单独的规则。
let和const
使用let和const声明的标识符具有块范围,除了直接在全局上下文中声明的标识符之外,在这种情况下,它们具有全局范围。
注:let、const和var均已吊装。这意味着其定义的逻辑位置是其封闭范围(块或函数)的顶部。然而,在控件通过源代码中的声明点之前,不能读取或分配使用let和const声明的变量。过渡期被称为时间死区。
函数f(){函数g(){console.log(x)}设x=1g()}f()//1,因为即使用“let”!
函数参数名称
函数参数名称的作用域是函数体。请注意,这有点复杂。声明为默认参数的函数关闭在参数列表上,而不是函数的主体上。
函数声明
函数声明在严格模式下具有块作用域,在非严格模式下有函数作用域。注意:非严格模式是一组复杂的突发规则,基于不同浏览器的古怪历史实现。
命名函数表达式
命名函数表达式的作用域仅限于自身(例如,用于递归)。
全局对象上隐式定义的财产
在非限定模式下,全局对象上隐式定义的财产具有全局范围,因为全局对象位于范围链的顶部。在严格模式下,这些是不允许的。
eval
在eval字符串中,使用var声明的变量将放在当前范围内,如果间接使用eval,则作为全局对象上的财产。
示例
下面将抛出ReferenceError,因为namesx、y和z在函数f之外没有意义。
函数f(){变量x=1设y=1常量z=1}console.log(typeof x)//未定义(因为var具有函数范围!)console.log(typeof y)//未定义(因为函数的主体是一个块)console.log(typeof z)//未定义(因为函数的主体是一个块)
下面将为y和z抛出ReferenceError,但不会为x抛出,因为x的可见性不受块的约束。定义if、for和while等控制结构体的块的行为类似。
{变量x=1设y=1常量z=1}console.log(x)//1console.log(typeof y)//未定义,因为“y”具有块范围console.log(typeof z)//未定义,因为“z”具有块范围
在下文中,x在循环外可见,因为var具有函数范围:
对于(var x=0;x<5;++x){}console.log(x)//5(注意这是在循环之外!)
…由于这种行为,您需要小心关闭在循环中使用var声明的变量。这里只声明了变量x的一个实例,它在逻辑上位于循环之外。
以下命令为循环外的console.log打印5次,然后第六次打印5次:
对于(var x=0;x<5;++x){setTimeout(()=>console.log(x))//在逻辑上位于封闭范围顶部的“x”上关闭,在循环上方}console.log(x)//注意:在循环外可见
以下打印未定义,因为x是块范围的。回调是一个接一个异步运行的。let变量的新行为意味着每个匿名函数都在一个名为x的不同变量上关闭(与var不同),因此会打印整数0到4
for(设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声明的变量的作用域是循环的主体:
对于(设x=0;x<10;++x){}console.log(typeof x)//未定义,因为“x”是块范围的
以下内容将引发ReferenceError,因为x的可见性受到块的约束:
if(false){设x=1}console.log(typeof x)//未定义,因为“x”是块范围的
使用var、let或const声明的变量的作用域都是模块:
// module1.js
var x = 0
export function f() {}
//module2.js
import f from 'module1.js'
console.log(x) // throws ReferenceError
下面将在全局对象上声明一个属性,因为在全局上下文中使用var声明的变量将作为财产添加到全局对象中:
变量x=1console.log(window.hasOwnProperty('x'))//true
全局上下文中的let和const不向全局对象添加财产,但仍具有全局范围:
设x=1console.log(window.hasOwnProperty('x'))//false
可以考虑在函数体中声明函数参数:
函数f(x){}console.log(typeof x)//未定义,因为“x”的作用域是函数
Catch块参数的作用域是Catch块体:
尝试{}捕获(e){}console.log(typeof e)//未定义,因为“e”的作用域是catch块
命名函数表达式的作用域仅限于表达式本身:
(函数foo(){console.log(foo)})()console.log(typeoffoo)//未定义,因为“foo”的作用域是它自己的表达式
在非限定模式下,全局对象上隐式定义的财产是全局范围的。在严格模式下,会出现错误。
x=1//隐式定义全局对象的属性(无“var”!)console.log(x)//1console.log(window.hasOwnProperty('x'))//true
在非严格模式下,函数声明具有函数范围。在严格模式下,它们具有块范围。
'使用严格'{函数foo(){}}console.log(foo类型)//未定义,因为“foo”是块范围的
发动机罩下的工作原理
作用域定义为标识符有效的代码词汇区域。
在JavaScript中,每个函数对象都有一个隐藏的[[Environment]]引用,该引用是对创建它的执行上下文(堆栈框架)的词法环境的引用。
调用函数时,将调用隐藏的[[Call]]方法。该方法创建新的执行上下文,并在新的执行环境和函数对象的词法环境之间建立链接。它通过将函数对象上的[[Environment]]值复制到新执行上下文的词法环境上的外部引用字段中来实现这一点。
注意,新的执行上下文和函数对象的词法环境之间的这种链接称为闭包。
因此,在JavaScript中,作用域是通过外部引用链接在一起的词汇环境实现的。这个词汇环境链称为作用域链,标识符解析是通过在链上搜索匹配的标识符来实现的。
了解更多信息。
最初由Brendan Eich设计的JavaScript中的作用域概念来自HyperCard脚本语言HyperTalk。
在这种语言中,显示类似于一堆索引卡。有一张主卡被称为背景。它是透明的,可以看作是底牌。此基础卡上的任何内容都与放在其上面的卡共享。放在上面的每张卡都有自己的内容,这些内容优先于前一张卡,但如果需要,仍然可以访问前一张卡片。
这正是JavaScript作用域系统的设计方式。它只是有不同的名字。JavaScript中的卡片称为Execution ContextsCMA。这些上下文中的每一个都包含三个主要部分。变量环境、词汇环境和此绑定。回到卡片参考,词汇环境包含堆栈中较低的先前卡片的所有内容。当前上下文位于堆栈的顶部,其中声明的任何内容都将存储在变量环境中。在命名冲突的情况下,变量环境将优先。
此绑定将指向包含对象。有时,作用域或执行上下文在包含对象不变的情况下发生变化,例如在声明的函数中,包含对象可能是窗口或构造函数。
这些执行上下文是在任何时候传输控制时创建的。当代码开始执行时,控制权被转移,这主要是通过函数执行完成的。
这就是技术解释。在实践中,记住在JavaScript中
范围在技术上是“执行上下文”上下文形成存储变量的环境堆栈堆栈的顶部优先(底部是全局上下文)每个函数都创建一个执行上下文(但不总是一个新的此绑定)
将此应用于前面的一个示例(5。“闭包”),可以遵循执行上下文的堆栈。在本例中,堆栈中有三个上下文。它们由外部上下文、var six调用的立即调用函数中的上下文以及var six立即调用函数内部返回函数的上下文定义。
i) 外部上下文。它的可变环境为a=1ii)IIFE上下文,它有一个a=1的词法环境,但一个a=6的变量环境在堆栈中优先iii)返回的函数上下文,它具有a=6的词法环境,这是调用时警报中引用的值。
JavaScript中有两种类型的作用域。
全局范围:在全局范围中宣布的变量可以在程序中的任何地方非常流畅地使用。例如:var carName=“BMW”;//这里的代码可以使用carName函数myFunction(){//这里的代码可以使用carName}函数作用域或局部作用域:在此作用域中声明的变量只能在其自身的函数中使用。例如://这里的代码不能使用carName函数myFunction(){var carName=“BMW”;//这里的代码可以使用carName}
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
试试这个奇怪的例子。在下面的示例中,如果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());
推荐文章
- 我如何检查如果一个变量是JavaScript字符串?
- 如何检测如果多个键被按下一次使用JavaScript?
- 如何通过history. pushstate获得历史变化的通知?
- 使用jQuery改变输入字段的类型
- 在JavaScript中,什么相当于Java的Thread.sleep() ?
- 使用jQuery以像素为整数填充或边距值
- 检查是否选择了jQuery选项,如果没有选择默认值
- Next.js React应用中没有定义Window
- 如何重置笑话模拟函数调用计数之前,每次测试
- 如何强制一个功能React组件渲染?
- 在javascript中从平面数组构建树数组
- 将Dropzone.js与其他字段集成到现有的HTML表单中
- 如何在AngularJS中观察路由变化?
- 为什么出现这个UnboundLocalError(闭包)?
- JavaScript DOM删除元素