javascript中的变量范围是什么?它们的内部是否与函数外部相同?或者甚至重要吗?另外,如果变量是全局定义的,那么它们存储在哪里?
答案 0 :(得分:2408)
我认为我能做的最好的事情就是给你一些学习的例子。 Javascript程序员实际上根据他们理解范围的程度来排名。 它有时可能非常违反直觉。
全局范围的变量
// global scope
var a = 1;
function one() {
alert(a); // alerts '1'
}
本地范围
// global scope
var a = 1;
function two(a) { // passing (a) makes it local scope
alert(a); // alerts the given argument, not the global value of '1'
}
// local scope again
function three() {
var a = 3;
alert(a); // alerts '3'
}
中级:没有JavaScript中的块范围(ES5; ES6引入let
)
一个。
var a = 1;
function four() {
if (true) {
var a = 4;
}
alert(a); // alerts '4', not the global value of '1'
}
湾
var a = 1;
function one() {
if (true) {
let a = 4;
}
alert(a); // alerts '1' because the 'let' keyword uses block scoping
}
中级:对象属性
var a = 1;
function Five() {
this.a = 5;
}
alert(new Five().a); // alerts '5'
高级:关闭
var a = 1;
var six = (function() {
var a = 6;
return function() {
// JavaScript "closure" means I have access to 'a' in here,
// because it is defined in the function in which I was defined.
alert(a); // alerts '6'
};
})();
高级:基于原型的范围解析
var a = 1;
function seven() {
this.a = 7;
}
// [object].prototype.property loses to
// [object].property in the lookup chain. For example...
// Won't get reached, because 'a' is set in the constructor above.
seven.prototype.a = -1;
// Will get reached, even though 'b' is NOT set in the constructor.
seven.prototype.b = 8;
alert(new seven().a); // alerts '7'
alert(new seven().b); // alerts '8'
全球+本地:一个额外的复杂案例
var x = 5;
(function () {
console.log(x);
var x = 10;
console.log(x);
})();
这将打印出undefined
和10
而不是5
和10
,因为JavaScript始终将变量声明(不是初始化)移动到范围的顶部,代码相当于:
var x = 5;
(function () {
var x;
console.log(x);
x = 10;
console.log(x);
})();
Catch子句范围的变量
var e = 5;
console.log(e);
try {
throw 6;
} catch (e) {
console.log(e);
}
console.log(e);
这将打印出5
,6
,5
。 catch子句e
内部隐藏了全局变量和局部变量。但是这个特殊范围仅适用于捕获的变量。如果你在catch子句中写var f;
,那么它就像你在try-catch块之前或之后定义它一样。
答案 1 :(得分:232)
Javascript使用范围链来建立给定函数的范围。通常有一个全局范围,每个定义的函数都有自己的嵌套范围。在另一个函数中定义的任何函数都有一个链接到外部函数的局部作用域。它始终是源中定义范围的位置。
作用域链中的元素基本上是一个带有指向其父作用域的指针的Map。
解析变量时,javascript从最里面的范围开始并向外搜索。
答案 2 :(得分:104)
全局声明的变量具有全局范围。在函数内声明的变量的范围限定为该函数,并且阴影全局变量具有相同名称。
(我确信真正的JavaScript程序员可以在其他答案中指出许多细微之处。特别是我遇到了this page关于this
在任何时候的含义。 this more introductory link足以让你开始。)
答案 3 :(得分:69)
传统上,JavaScript实际上只有两种类型的范围:
我不会详细说明这一点,因为已经有许多其他答案解释了这一点。
most recent JavaScript specs现在还允许第三个范围:
传统上,您可以像这样创建变量:
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
仅在第一个for循环中已知,但在之前和之后都不知道。然而,我们的变量i
在整个函数中都是已知的。
另外,请考虑块范围变量在声明之前是未知的,因为它们没有被提升。您也不允许在同一块中重新声明相同的块范围变量。这使得块范围变量比全局或功能范围变量更不容易出错,这些变量被提升并且在多个声明的情况下不会产生任何错误。
今天是否安全使用取决于您的环境:
如果您正在编写服务器端JavaScript代码(Node.js),则可以安全地使用let
语句。
如果您正在编写客户端JavaScript代码并使用基于浏览器的转换器(如Traceur或babel-standalone),则可以安全地使用let
语句但是,就性能而言,您的代码可能不是最优的。
如果您正在编写客户端JavaScript代码并使用基于节点的转换器(例如traceur shell script或Babel),则可以安全地使用let
声明。而且因为您的浏览器只会知道转换后的代码,所以应该限制性能缺陷。
如果您正在编写客户端JavaScript代码并且不使用转换器,则需要考虑浏览器支持。
这些浏览器根本不支持let
:
有关哪些浏览器在您阅读此答案时支持let
语句的最新概述,请参阅this Can I Use
page。
(*)全局和功能范围的变量可以在声明它们之前进行初始化和使用,因为JavaScript变量是hoisted。这意味着声明始终位于范围的顶部
(**)未提升块范围变量
答案 4 :(得分:38)
以下是一个例子:
<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>
您需要调查闭包,以及如何使用它们来制作private members。
答案 5 :(得分:31)
据我所知,关键是Javascript具有功能级别范围与更常见的C块范围。
答案 6 :(得分:26)
在“Javascript 1.7”(Mozilla对Javascript的扩展)中,还可以使用let
statement声明块范围变量:
var a = 4;
let (a = 3) {
alert(a); // 3
}
alert(a); // 4
答案 7 :(得分:22)
最初由Brendan Eich设计的JavaScript范围来自HyperCard脚本语言HyperTalk。
在这种语言中,显示与一堆索引卡类似。有一张主卡称为背景。它是透明的,可以看作底卡。此基卡上的任何内容都与放置在其上的卡共享。放在顶部的每张卡片都有自己的内容,优先于之前的卡片,但如果需要,仍然可以访问之前的卡片。
这正是JavaScript作用域系统的设计方式。它只是有不同的名称。 JavaScript中的卡片称为 Execution ContextsECMA 。这些背景中的每一个都包含三个主要部分。变量环境,词法环境和此绑定。回到卡片参考,词汇环境包含堆栈中较低的先前卡片的所有内容。当前上下文位于堆栈的顶部,并且在那里声明的任何内容都将存储在变量环境中。在命名冲突的情况下,变量环境将优先。
此绑定将指向包含对象。有时,范围或执行上下文会在不更改包含对象的情况下发生更改,例如在包含对象可能为window
的已声明函数或构造函数中。
这些执行上下文是在传输控件的任何时候创建的。当代码开始执行时,控制被转移,这主要是通过函数执行完成的。
这就是技术解释。在实践中,重要的是要记住在JavaScript中
将此应用于此页面上的前一个示例之一(5.“Closure”),可以遵循执行上下文的堆栈。在此示例中,堆栈中有三个上下文。它们由外部上下文,var 6调用的立即调用函数中的上下文以及var 6中立即调用函数内返回函数中的上下文定义。
i )外部背景。它具有a = 1的可变环境
ii )IIFE上下文,它有一个a = 1的词汇环境,但a = 6的变量环境优先于堆栈
iii )返回的函数上下文,它具有a = 6的词法环境,并且是调用时警报中引用的值。
答案 8 :(得分:17)
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.test1。 'var test1'在上部词汇上下文(函数或全局)中创建一个新的变量test1,除非它是a的属性 - 它是什么。
糟糕!小心使用'with' - 如果变量已经在函数中定义,就像var是noop一样,对于从对象导入的名称,它也是一个noop!对已经定义的名称进行一点点提醒会使这更加安全。我个人永远不会因此而使用。
答案 9 :(得分:10)
我发现许多刚接触JavaScript的人很难理解默认情况下语言中的继承是可用的,到目前为止,函数范围是唯一的范围。我为去年年底写的一个名为JSPretty的美化家提供了扩展。功能颜色代码中的函数范围,并始终将颜色与该范围中声明的所有变量相关联。当在不同范围内使用来自一个范围的颜色的变量时,可以直观地演示闭包。
尝试以下功能:
观看演示:
查看以下代码:
目前,该功能支持深度为16的嵌套函数,但目前不对全局变量着色。
答案 10 :(得分:9)
JavaScript只有两种范围:
var
关键字的函数中声明的变量具有功能范围。每当调用一个函数时,都会创建一个变量范围对象(并包含在范围链中),后面跟着JavaScript中的变量。
a = "global";
function outer(){
b = "local";
console.log(a+b); //"globallocal"
}
outer();
范围链 - &gt;
a
和outer
功能位于范围链的顶层。variable scope object
(并包含在作用域链中)时,其中添加了变量b
。现在当一个变量a
需要它时,它首先搜索最近的变量范围,如果变量不存在,那么它将移动到变量范围链的下一个对象。在这种情况下,它是窗口级别。
答案 11 :(得分:9)
只是为了添加其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则,以确定当前执行的代码如何访问它们。该查找可以用于分配给变量的目的,该变量是LHS(左手侧)参考,或者它可以用于检索其值,即RHS(右手侧)参考。这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作。
所以从这个角度来看,我认为一张图片可以帮助我在Kyle Simpson的Scopes and Closures电子书中找到:
引用他的电子书:
该建筑代表我们程序的嵌套范围规则集。首先 建筑物的楼层代表您当前执行的范围, 无论你在哪。建筑的顶层是全球范围。 您可以通过查看当前楼层来解决LHS和RHS参考, 如果你没找到,乘电梯到下一层, 看那里,然后下一个,依此类推。一旦你到达顶层 (全球范围),您要么找到您要找的东西,要么找到您 别。但你不得不停下来。
值得一提的是,#34;一旦找到第一场比赛&#34,范围查找就会停止。
这个&#34;范围级别的想法&#34;解释了为什么&#34;这个&#34;如果要在嵌套函数中查找,可以使用新创建的范围进行更改。 以下是所有这些详细信息的链接Everything you wanted to know about javascript scope
答案 12 :(得分:8)
全球变数与全球明星(成龙,纳尔逊曼德拉)完全一样。您可以从应用程序的任何部分访问它们(获取或设置值)。全球活动就像全球活动(新年,圣诞节)。您可以从应用程序的任何部分执行(调用)它们。
//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
答案 13 :(得分:8)
运行代码。希望这会给出一个关于范围界定的想法
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);
答案 14 :(得分:6)
ALMOST只有两种类型的JavaScript范围:
因此,除函数之外的任何块都不会创建新范围。这解释了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范围所以你可以看到JavaScript范围实际上非常简单,尽管并不总是直观的。有几点需要注意:
所以这段代码:
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);
这看似违反直觉,但从命令式语言设计师的角度来看,这是有道理的。
答案 15 :(得分:5)
const
'和'let
'您应该为您创建的每个变量使用块范围,就像大多数其他主要语言一样。 var
已过时。这使您的代码更安全,更易于维护。
const
应该用于 95%的案例。它使得变量引用无法改变。数组,对象和DOM节点属性可以更改,并且可能应为const
。
let
应该用于任何期望重新分配的变量。这包括在for循环中。如果您更改了初始化之外的值,请使用let
。
块范围意味着该变量仅在声明它的括号内可用。这扩展到内部范围,包括在范围内创建的匿名函数。
答案 16 :(得分:4)
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);
答案 17 :(得分:3)
试试这个好奇的例子。在下面的例子中,如果a是一个初始化为0的数字,你会看到0然后是1.除了a是一个对象,javascript将传递f1的指针而不是它的副本。结果是您两次都得到相同的警报。
var a = new Date();
function f1(b)
{
b.setDate(b.getDate()+1);
alert(b.getDate());
}
f1(a);
alert(a.getDate());
答案 18 :(得分:3)
我的理解是有三个范围:全球范围,全球可用;本地范围,可用于整个功能而不管块;和块作用域,仅适用于使用它的块,语句或表达式。全局和本地范围在关键字&#39; var&#39;中指示,在函数内或外部,块范围用关键字&#39; let&#39;表示。
对于那些认为只有全局和局部范围的人,请解释为什么Mozilla会有一整页描述JS中块范围的细微差别。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
答案 19 :(得分:2)
在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
}
}
答案 20 :(得分:1)
在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.
}
答案 21 :(得分:1)
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
答案 22 :(得分:1)
ES5
和更早版本:最初,JavaScript中的变量在词法函数范围内(ES6
之前)。词法范围的术语意味着您可以通过“查看”代码来查看变量的范围。
使用var
关键字声明的每个变量都作用于该函数。但是,如果在该函数中声明了其他函数,则这些函数将有权访问外部函数的变量。这称为作用域链。它以以下方式工作:
// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';
function outerFunc () {
// outerFunc scope
var foo = 'outerFunc';
var foobar = 'outerFunc';
innerFunc();
function innerFunc(){
// innerFunc scope
var foo = 'innerFunc';
console.log(foo);
console.log(bar);
console.log(foobar);
}
}
outerFunc();
当我们尝试将变量foo
,bar
和foobar
记录到控制台时会发生以下情况:
innerFunc
本身中找到foo。因此,foo的值将解析为字符串innerFunc
。innerFunc
本身内部找不到bar。因此,我们需要攀登范围链。我们首先查看定义了函数innerFunc
的外部函数。这是功能outerFunc
。在outerFunc
的范围内,我们可以找到变量bar,其中包含字符串'outerFunc'。 ES6
(ES 2015)及更低版本:词汇范围和作用域链的相同概念仍然适用于ES6
。但是,引入了一种声明变量的新方法。有以下内容:
let
:创建一个块范围变量const
:创建一个块范围的变量,该变量必须初始化且不能重新分配 var
和let
/ const
之间的最大区别是var
是函数范围的,而let
/ const
是块范围的。这是一个说明这一点的示例:
let letVar = 'global';
var varVar = 'global';
function foo () {
if (true) {
// this variable declared with let is scoped to the if block, block scoped
let letVar = 5;
// this variable declared with let is scoped to the function block, function scoped
var varVar = 10;
}
console.log(letVar);
console.log(varVar);
}
foo();
在上面的示例中,letVar记录全局值,因为用let
声明的变量是块作用域的。它们不再存在于各自的块外,因此无法在if块外访问变量。
答案 23 :(得分:0)
前端编码器经常遇到的一个非常常见的问题(尚未描述)是HTML中的内联事件处理程序可见的范围-例如,
<button onclick="foo()"></button>
on*
属性可以引用 的变量的范围必须为:
querySelector
作为独立变量将指向document.querySelector
;罕见)否则,调用处理程序时,您将收到ReferenceError。因此,例如,如果内联处理程序引用的是 inside window.onload
或$(function() {
定义的函数,则该引用将失败,因为内联处理程序可能仅引用了全局范围,并且该函数不是全局的:
window.addEventListener('DOMContentLoaded', () => {
function foo() {
console.log('foo running');
}
});
<button onclick="foo()">click</button>
document
的属性和附加到处理程序的元素的属性也可以在内联处理程序中引用为独立变量,因为内联处理程序被调用inside of two with
blocks,document
,一个用于元素。这些处理程序中变量的作用域链为extremely unintuitive,工作事件处理程序将可能要求一个函数为全局函数(以及不必要的全局污染should probably be avoided)。
由于内联处理程序中的作用域链太奇怪了,并且由于内联处理程序需要全局污染才能起作用,因此避免它们可能更容易。而是使用Javascript(例如,使用addEventListener
)而不是使用HTML标记来附加事件处理程序。
function foo() {
console.log('foo running');
}
document.querySelector('.my-button').addEventListener('click', foo);
<button class="my-button">click</button>
答案 24 :(得分:0)
我真的很喜欢被接受的答案,但是我想补充一下:
Scope收集并维护所有已声明的标识符(变量)的查找列表,并针对当前执行的代码如何访问使用严格的规则。
范围是用于通过变量标识符名称查找变量的一组规则。
答案 25 :(得分:0)
(function foo() { console.log(foo) })();
console.log(typeof foo); // undefined, because `foo` is scoped to its own expression
//but, like this
(function foo() {
console.log('1:', foo) // function foo
foo = 100
console.log('2:', foo) // function foo, is not 100, why?
})()
答案 26 :(得分:-2)
JavaScript中有两种类型的范围。
全球范围:在全球范围内公布的变量可以非常顺利地在程序中的任何位置使用。例如:
var carName = " BMW";
// code here can use carName
function myFunction() {
// code here can use carName
}
功能范围或本地范围:在此范围内声明的变量只能在其自己的函数中使用。例如:
// code here can not use carName
function myFunction() {
var carName = "BMW";
// code here can use carName
}