javascript中变量的范围是什么?它们在函数内部的作用域与在函数外部的作用域相同吗?或者这有关系吗?此外,如果变量是全局定义的,那么变量存储在哪里?
全局声明的变量具有全局范围。在函数中声明的变量的作用域是该函数,并隐藏同名的全局变量。
(我相信真正的JavaScript程序员可以在其他答案中指出许多微妙之处。特别是我在这个页面上看到了这到底意味着什么。不过,希望这个更为介绍性的链接足以让你开始。)
Javascript使用作用域链为给定函数建立作用域。通常有一个全局范围,每个定义的函数都有自己的嵌套范围。在另一个函数中定义的任何函数都具有与外部函数链接的局部作用域。定义范围的始终是源代码中的位置。
作用域链中的元素基本上是一个带有指向其父作用域指针的Map。
解析变量时,javascript从最内部的范围开始并向外搜索。
下面是一个示例:
<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>
您将需要调查闭包,以及如何使用它们创建私有成员。
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中,作用域是通过外部引用链接在一起的词汇环境实现的。这个词汇环境链称为作用域链,标识符解析是通过在链上搜索匹配的标识符来实现的。
了解更多信息。
在“Javascript 1.7”(Mozilla对Javascript的扩展)中,还可以使用let语句声明块范围变量:
var a = 4;
let (a = 3) {
alert(a); // 3
}
alert(a); // 4
我发现许多新接触JavaScript的人很难理解继承在默认情况下在语言中是可用的,而函数作用域是迄今为止唯一的作用域。我为去年年底我写的一个名为JSprety的美化者提供了一个扩展。功能为代码中的函数范围着色,并始终将颜色与该范围中声明的所有变量相关联。当在不同的范围中使用来自一个范围的颜色的变量时,可以直观地演示闭包。
尝试以下功能:
http://prettydiff.com/jspretty.xhtml?c=white&jsscope
观看演示:
http://prettydiff.com/jspretty.xhtml?c=white&jsscope&s=http://prettydiff.com/lib/markup_beauty.js
在以下位置查看代码:
http://prettydiff.com/lib/jspretty.jshttps://github.com/austincheney/Pretty-Diff/blob/master/lib/jspretty.js
目前,该功能支持深度为16个嵌套函数,但目前不为全局变量着色。
试试这个奇怪的例子。在下面的示例中,如果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());
1) 有一个全局作用域、一个函数作用域以及with和catch作用域。一般来说,变量的作用域没有“块”级别的作用域——with和catch语句为其块添加名称。
2) 作用域一直由函数嵌套到全局作用域。
3) 通过原型链解决财产问题。with语句将对象属性名称带入with块定义的词法范围。
编辑:ECMAAScript 6(Harmony)被指定为支持let,我知道chrome允许一个“和谐”标志,所以也许它确实支持它。。
这将是对块级范围的支持,但必须使用关键字才能实现。
编辑:根据本杰明在评论中指出的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.test1Vartest1'在上层词法上下文(函数或全局)中创建一个新的变量test1,除非它是一个属性——它就是这样。
诶呀小心使用'with'——就像var是noop一样,如果变量已经在函数中定义,那么它也是从对象导入的名称的noop!对已经定义的名称稍加注意会使其更加安全。因此,我个人永远不会使用。
全球范围:
全球变量就像全球明星(成龙、纳尔逊·曼德拉)。您可以从应用程序的任何部分访问它们(获取或设置值)。全球功能就像全球活动(新年、圣诞节)。您可以从应用程序的任何部分执行(调用)它们。
//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
查看本文以深入了解范围
JavaScript只有两种类型的作用域:
全局作用域:全局只是一个窗口级作用域。在这里,整个应用程序中都存在变量。Functional Scope:在带有var关键字的函数中声明的变量具有函数作用域。
每当调用一个函数时,就会创建一个变量范围对象(并包含在范围链中),然后是JavaScript中的变量。
a = "global";
function outer(){
b = "local";
console.log(a+b); //"globallocal"
}
outer();
作用域链-->
窗口级别-a和外部函数位于范围链的顶层。当外部函数调用一个新的变量作用域对象(并包含在作用域链中)时,在其内部添加了变量b。
现在,当一个变量a需要时,它首先搜索最近的变量范围,如果变量不在,则将其移动到变量范围链的下一个对象。在这种情况下,这是窗口级别。
运行代码。希望这能提供一个关于范围界定的想法
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);
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);
最初由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}
几乎只有两种类型的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
在第一个示例中,没有块范围,因此最初声明的变量被覆盖。在第二个示例中,由于函数的原因,出现了一个新的作用域,因此最初声明的变量是SHADOWED,而不是重写。
就JavaScript范围而言,这几乎是您需要了解的全部内容,除了:
try/catch仅为异常变量本身引入新范围,其他变量没有新范围with子句显然是另一个例外,但不鼓励使用with子句(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with)
因此,您可以看到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);
这似乎与直觉相悖,但从命令式语言设计者的角度来看,这是有道理的。
老式JavaScript
传统上,JavaScript实际上只有两种类型的作用域:
全局范围:从应用程序开始,变量在整个应用程序中都是已知的(*)函数作用域:变量在声明它们的函数中是已知的,从函数开始(*)
我不会详细说明这一点,因为已经有许多其他答案可以解释这一差异。
现代JavaScript
最新的JavaScript规范现在还允许第三个范围:
块作用域:标识符从其声明的作用域的顶部开始“已知”,但在其声明的行之后才能分配或取消引用(读取)。这一过渡时期被称为“时间死区”
如何创建块范围变量?
传统上,您可以这样创建变量:
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只在first for循环中知道,而在前后都不知道。然而,我们的变量i在整个函数中是已知的。
此外,考虑到块范围的变量在声明之前是未知的,因为它们没有被提升。也不允许在同一块内重新声明同一块范围内的变量。这使得块作用域变量比全局或函数作用域变量更不容易出错,这些变量被提升,在多个声明的情况下不会产生任何错误。
现在使用块范围变量安全吗?
今天使用它是否安全取决于您的环境:
如果您正在编写服务器端JavaScript代码(Node.js),则可以安全地使用let语句。如果您正在编写客户端JavaScript代码并使用基于浏览器的转译器(如Traceur或babel standalone),则可以安全地使用let语句,但是您的代码在性能方面可能不是最佳的。如果您正在编写客户端JavaScript代码并使用基于节点的转译器(如traceur shell脚本或Babel),则可以安全地使用let语句。而且,由于您的浏览器将只知道转译的代码,因此性能缺陷应该受到限制。如果您正在编写客户端JavaScript代码,而不使用转译器,则需要考虑浏览器支持。以下是一些根本不支持let的浏览器:Internet explorer 10及以下Firefox 43及以下Safari 9及以下Android浏览器4及以下版本Opera 27及以下Chome 40及以下Opera Mini和黑莓浏览器的任何版本
如何跟踪浏览器支持
有关阅读此答案时哪些浏览器支持let语句的最新概述,请参阅“我可以使用”页面。
(*)全局和函数范围的变量可以在声明之前初始化和使用,因为JavaScript变量被提升。这意味着声明总是位于范围的顶部。
为了补充其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则,以确定当前执行的代码如何访问这些标识符。该查找可能用于分配变量,该变量是LHS(左侧)引用,也可能用于检索其值,该值是RHS(右侧)引用。这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作。
因此,从这个角度来看,我认为我在凯尔·辛普森(Kyle Simpson)的《范围与关闭》(Scopes and Closures)电子书中找到的一张图片会有所帮助:
引用他的电子书:
该构建表示程序的嵌套范围规则集。第一个建筑物的楼层代表您当前执行的范围,无论你在哪里。建筑的顶层是全球范围。通过查看当前楼层来解析LHS和RHS参考,如果你找不到,就乘电梯到下一层,看那里,然后看下一个,以此类推。一旦你到达顶层(全球范围),你要么找到你想要的,要么不要。但你必须停止不管。
值得一提的是,“范围查找一旦找到第一个匹配项就停止”。
“作用域级别”的概念解释了如果在嵌套函数中查找“This”,为什么可以使用新创建的作用域来更改“This”。这里有一个链接,介绍了所有这些细节,您想了解的关于javascript范围的一切
我的理解是有三个范围:全球范围,全球可用;局部范围,无论块如何,整个功能都可用;和块范围,仅对使用它的块、语句或表达式可用。全局和局部作用域用关键字“var”表示,无论是在函数内还是在函数外,块作用域都用关键字“let”表示。
对于那些认为只有全局和局部范围的人,请解释为什么Mozilla会有一个完整的页面来描述JS中块范围的细微差别。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
现代J、ES6+、“const”和“let”
您应该像大多数其他主要语言一样,为创建的每个变量使用块范围。var已过时。这使您的代码更安全,更易于维护。
95%的病例应使用常量。它使变量引用不能更改。数组、对象和DOM节点财产可以更改,并且应该是常量。
let应该用于任何期望重新分配的变量。这包括在for循环中。如果在初始化之后更改值,请使用let。
块范围意味着变量只能在声明它的括号内使用。这扩展到内部范围,包括在范围内创建的匿名函数。
在JavaScript中,有两种类型的作用域:
本地范围全局范围
Below函数有一个局部范围变量carName。这个变量不能从函数外部访问。
function myFunction() {
var carName = "Volvo";
alert(carName);
// code here can use carName
}
Below Class有一个全局范围变量carName。这个变量可以从类中的任何地方访问。
class {
var carName = " Volvo";
// code here can use carName
function myFunction() {
alert(carName);
// code here can use carName
}
}
在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.
}
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
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块之外访问变量。
内联处理程序
前端代码编写者经常遇到的一个尚未描述的非常常见的问题是HTML中内联事件处理程序可见的范围,例如
<button onclick="foo()"></button>
on*属性可以引用的变量范围必须是:
全局(工作内联处理程序几乎总是引用全局变量)文档的属性(例如,作为独立变量的querySelector将指向document.querySelector;罕见)处理程序附加到的元素的属性(如上所述;罕见)
否则,当调用处理程序时,将得到ReferenceError。因此,例如,如果内联处理程序引用了在window.onload或$(function){中定义的函数,则引用将失败,因为内联处理程序只能引用全局范围内的变量,而函数不是全局的:
window.addEventListener('DOMContentLoaded',()=>{函数foo(){console.log('运行');}});<button onclick=“foo()”>单击</button>
文档的财产和处理程序附加到的元素的财产也可以作为内联处理程序中的独立变量引用,因为内联处理函数是在两个块中调用的,一个用于文档,另一个用于元素。这些处理程序中变量的作用域链非常不直观,并且一个工作的事件处理程序可能需要一个函数是全局的(并且应该避免不必要的全局污染)。
由于内联处理程序内部的作用域链非常奇怪,而且内联处理程序需要全局污染才能工作,并且内联处理程序在传递参数时有时需要丑陋的字符串转义,因此可能更容易避免它们。相反,使用Javascript(如addEventListener)而不是HTML标记附加事件处理程序。
函数foo(){console.log('运行');}document.querySelector('.my button').addEventListener('click',foo);<button class=“my button”>单击</button>
模块(<script type=“module”>)
另一方面,与在顶层运行的普通<script>标记不同,ES6模块内的代码在其自己的私有范围内运行。在普通<script>标记顶部定义的变量是全局的,因此您可以在其他<script>标签中引用它,如下所示:
<脚本>const foo=“foo”;</script><脚本>console.log(foo);</script>
但是ES6模块的顶层不是全局的。在ES6模块顶部声明的变量仅在该模块内部可见,除非该变量被显式导出,或者除非它被分配给全局对象的属性。
<script type=“module”>const foo=“foo”;</script><脚本>//无法在此处访问foo,因为其他脚本是模块console.log(foo类型);</script>
ES6模块的顶层与普通<script>中顶层IIFE内部的顶层类似。模块可以引用任何全局变量,除非模块为其明确设计,否则任何内容都不能引用模块内部的任何内容。
我很喜欢接受的答案,但我想补充一点:
作用域收集并维护所有声明的标识符(变量)的查找列表,并对当前执行的代码如何访问这些标识符实施一组严格的规则。
作用域是一组按标识符名称查找变量的规则。
如果在直接作用域中找不到变量,引擎将继续查询下一个外部包含作用域,直到找到或到达最外部(即全局)作用域。是一组规则,用于确定在何处以及如何查找变量(标识符)。该查找可能用于分配变量,该变量是LHS(左侧)参考,也可能用于检索其值,该值是RHS(右侧)参考。LHS引用源于赋值操作。与作用域相关的赋值可以使用=运算符进行,也可以通过将参数传递给(赋值给)函数参数进行。JavaScript引擎在执行代码之前首先编译代码,在这样做的过程中,它会拆分像vara=2这样的语句;分为两个单独的步骤:第一步。首先,var a在该范围中声明它。这是在代码执行之前开始执行的。第二。稍后,a=2查找变量(LHS引用),如果找到,则将其赋值。LHS和RHS引用查找都从当前执行的作用域开始,如果需要的话(也就是说,它们找不到它们在那里寻找的对象),它们会在嵌套作用域中一次一个作用域(层)查找标识符,直到它们到达全局(顶层)并停止,然后要么找到,要么不找到。未完成的RHS引用导致引发ReferenceError。未完成的LHS引用将导致该名称的自动隐式创建的全局(如果不在严格模式下),或ReferenceError(如果在严格模式中)。作用域由一系列“气泡”组成,每个气泡充当容器或桶,其中声明了标识符(变量、函数)。这些气泡整齐地嵌套在彼此内部,这种嵌套是在作者时定义的。
(函数foo(){console.log(foo)})();console.log(foo类型);//未定义,因为“foo”的作用域是它自己的表达式//但是,像这样(函数foo(){console.log('1:',foo)//函数foofoo=100console.log('2:',foo)//函数foo,不是100,为什么?})()
推荐文章
- 什么时候JavaScript是同步的?
- 如何在Typescript中解析JSON字符串
- Javascript reduce()在对象
- 在angularJS中& vs @和=的区别是什么
- 错误"Uncaught SyntaxError:意外的标记与JSON.parse"
- JavaScript中的querySelector和querySelectorAll vs getElementsByClassName和getElementById
- 给一个数字加上st, nd, rd和th(序数)后缀
- 函数可以作为参数传递吗?
- 如何以编程方式触发引导模式?
- setTimeout带引号和不带括号的区别
- 在JS的Chrome CPU配置文件中,'self'和'total'之间的差异
- 用javascript检查输入字符串中是否包含数字
- 如何使用JavaScript分割逗号分隔字符串?
- 在Javascript中~~(“双波浪号”)做什么?
- 谷歌chrome扩展::console.log()从后台页面?