lYr*_*sSH 1952 javascript variables scope var function
javascript中的变量范围是什么?它们的内部是否与函数外部相同?或者甚至重要吗?另外,如果变量是全局定义的,那么它们存储在哪里?
Tri*_*ych 2476
我认为我能做的最好的事情就是给你一些学习的例子.Javascript程序员实际上根据他们理解范围的程度来排名.它有时可能非常违反直觉.
全局范围的变量
// global scope
var a = 1;
function one() {
alert(a); // alerts '1'
}
Run Code Online (Sandbox Code Playgroud)本地范围
// 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'
}
Run Code Online (Sandbox Code Playgroud)中级:没有JavaScript中的块范围(ES5; ES6介绍let)
一个.
var a = 1;
function four() {
if (true) {
var a = 4;
}
alert(a); // alerts '4', not the global value of '1'
}
Run Code Online (Sandbox Code Playgroud)
湾
var a = 1;
function one() {
if (true) {
let a = 4;
}
alert(a); // alerts '1' because the 'let' keyword uses block scoping
}
Run Code Online (Sandbox Code Playgroud)中级:对象属性
var a = 1;
function one() {
if (true) {
const a = 4;
}
alert(a); // alerts '1' because the 'const' keyword also uses block scoping as 'let'
}
Run Code Online (Sandbox Code Playgroud)高级:关闭
var a = 1;
function Five() {
this.a = 5;
}
alert(new Five().a); // alerts '5'
Run Code Online (Sandbox Code Playgroud)高级:基于原型的范围解析
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'
};
})();
Run Code Online (Sandbox Code Playgroud)
全球+本地:一个额外复杂的案例
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'
Run Code Online (Sandbox Code Playgroud)
这将打印出undefined和10而不是5和10自JavaScript的始终移动变量声明(未初始化)的范围的顶部,使得代码等同于:
var x = 5;
(function () {
console.log(x);
var x = 10;
console.log(x);
})();
Run Code Online (Sandbox Code Playgroud)Catch子句范围的变量
var x = 5;
(function () {
var x;
console.log(x);
x = 10;
console.log(x);
})();
Run Code Online (Sandbox Code Playgroud)
这将打印出来5,6,5.在catch子句中e隐藏全局变量和局部变量.但是这个特殊范围仅适用于捕获的变量.如果你var f;在catch子句中写入,那么它就像你在try-catch块之前或之后定义它一样.
kro*_*old 232
Javascript使用范围链来确定给定函数的范围.通常有一个全局范围,每个定义的函数都有自己的嵌套范围.在另一个函数中定义的任何函数都有一个链接到外部函数的局部作用域.始终是源中定义范围的位置.
范围链中的元素基本上是一个带有指向其父范围的指针的Map.
解析变量时,javascript从最里面的范围开始并向外搜索.
Jon*_*eet 107
全球宣布的变量具有全球范围.在函数内声明的变量作用于该函数,并且阴影全局变量具有相同名称.
(我确信真正的JavaScript程序员可以在其他答案中指出很多细微之处.特别是我在这个页面上看到了this任何时候究竟意味着什么.希望这个更多的介绍性链接足以让你开始.)
Joh*_*ers 75
传统上,JavaScript实际上只有两种类型的范围:
我不会详细说明这一点,因为已经有许多其他答案解释了这一点.
在最近JavaScript的功能现在也允许第三范围:
传统上,您可以像这样创建变量:
var myVariable = "Some text";
Run Code Online (Sandbox Code Playgroud)
块范围变量的创建方式如下:
let myVariable = "Some text";
Run Code Online (Sandbox Code Playgroud)
要了解功能范围和块范围之间的区别,请考虑以下代码:
// 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
Run Code Online (Sandbox Code Playgroud)
在这里,我们可以看到我们的变量j只在第一个for循环中被知道,而不是在之前和之后.然而,我们的变量i在整个函数中是已知的.
另外,请考虑块范围变量在声明之前是未知的,因为它们不会被提升.您也不允许在同一块中重新声明相同的块范围变量.这使得块范围变量比全局或功能范围变量更不容易出错,这些变量被提升并且在多个声明的情况下不会产生任何错误.
今天是否安全使用取决于您的环境:
如果您正在编写服务器端JavaScript代码(Node.js),则可以安全地使用该let语句.
如果您正在编写客户端JavaScript代码并使用转换器(如Traceur),则可以安全地使用该let语句,但是您的代码在性能方面可能不是最优的.
如果您正在编写客户端JavaScript代码而不使用转换器,则需要考虑浏览器支持.
今天,2016年2月23日,这些是一些不支持let或只支持部分支持的浏览器:
有关let在阅读本答案时哪些浏览器支持该语句的最新概述,请参阅此let页面.
(*)全局和功能范围的变量可以在声明之前初始化和使用,因为JavaScript变量是悬挂的.这意味着声明始终位于范围的顶部.
(**)未提升块范围变量
geo*_*wa4 39
这是一个例子:
<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>
Run Code Online (Sandbox Code Playgroud)
您将要研究闭包,以及如何使用它们来创建私有成员.
ken*_*ytm 26
在"Javascript 1.7"(Mozilla对Javascript的扩展)中,还可以使用let语句声明块范围变量:
var a = 4;
let (a = 3) {
alert(a); // 3
}
alert(a); // 4
Run Code Online (Sandbox Code Playgroud)
Tra*_*s J 25
最初由Brendan Eich设计的JavaScript范围来自HyperCard脚本语言HyperTalk.
在这种语言中,显示的操作类似于一堆索引卡.有一张主卡称为背景.它是透明的,可以看作底卡.此基卡上的任何内容都与放置在其上的卡共享.放在顶部的每张卡片都有自己的内容,该内容优先于之前的卡片,但如果需要,仍然可以访问之前的卡片.
这正是JavaScript作用域系统的设计方式.它只是有不同的名称.JavaScript中的卡片称为执行上下文ECMA.这些背景中的每一个都包含三个主要部分.变量环境,词法环境和此绑定.回到卡片参考,词汇环境包含堆栈中较低的先前卡片的所有内容.当前上下文位于堆栈的顶部,并且在那里声明的任何内容都将存储在变量环境中.在命名冲突的情况下,变量环境将优先.
此绑定将指向包含对象.有时,范围或执行上下文会在不包含对象更改的情况下发生更改,例如在包含对象的声明函数中window或构造函数中.
在传输控制的任何时候都会创建这些执行上下文.当代码开始执行时,控制被转移,这主要是通过函数执行完成的.
这就是技术解释.在实践中,重要的是要记住在JavaScript中
将此应用于此页面上的前一个示例之一(5."Closure"),可以遵循执行上下文的堆栈.在此示例中,堆栈中有三个上下文.它们由外部上下文,var 6调用的立即调用函数中的上下文以及var 6中立即调用的函数内部返回函数中的上下文定义.
i)外在背景.它有一个变量环境a = 1
ii)IIFE上下文,它有一个a = 1的词法环境,但a = 6的变量环境优先于堆栈
iii)返回的函数上下文,它有一个词汇a = 6的环境,这是调用时警报中引用的值.
Ger*_*ill 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'
Run Code Online (Sandbox Code Playgroud)
编辑:澄清示例:
test1的作用域为with块,但是别名为a.test1.'var test1'在上部词汇上下文(函数或全局)中创建一个新的变量test1,除非它是a的属性 - 它是什么.
哎呀!小心使用'with' - 如果变量已经在函数中定义,就像var是noop一样,对于从对象导入的名称,它也是一个noop!对已经定义的名称进行一点点提醒会使这更加安全.因为这个原因,我个人永远不会使用.
aus*_*ney 10
我发现许多刚接触JavaScript的人很难理解默认情况下语言中的继承是可用的,到目前为止,函数范围是唯一的范围.我为去年年底写的一个名为JSPretty的美化家提供了扩展.功能颜色代码中的函数范围,并始终将颜色与该范围中声明的所有变量相关联.当在不同范围内使用具有来自一个范围的颜色的变量时,可视地演示闭合.
尝试以下功能:
观看演示:
查看以下代码:
目前,该功能支持深度为16的嵌套函数,但目前不对全局变量着色.
JavaScript只有两种类型的范围:
var关键字的函数内声明的变量具有功能范围.无论何时调用函数,都会创建一个变量范围对象(并包含在范围链中),后面跟着JavaScript中的变量.
a = "global";
function outer(){
b = "local";
console.log(a+b); //"globallocal"
}
outer();
Run Code Online (Sandbox Code Playgroud)
范围链 - >
a和outer功能在范围链中处于顶层.variable scope object(并包含在作用域链中)中添加变量b时.现在当一个变量a需要它时,它首先搜索最近的变量范围,如果变量不存在,那么它将移动到变量范围链的下一个对象.在这种情况下,它是窗口级别.
只是为了添加其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则,以确定当前执行的代码如何访问它们.该查找可以用于分配给变量的目的,该变量是LHS(左手侧)参考,或者它可以用于检索其值,即RHS(右手侧)参考.这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作.
所以从这个角度来看,我认为一张图片可以帮助我在Kyle Simpson的Scopes and Closures电子书中找到:
引用他的电子书:
该建筑代表我们程序的嵌套范围规则集.无论您身在何处,建筑的第一层代表您当前执行的范围.建筑的顶层是全球范围.您可以通过查看当前楼层来解决LHS和RHS参考,如果找不到,请将电梯带到下一层,然后查看下一层,依此类推.一旦你到达顶层(全球范围),你要么找到你要找的东西,要么找不到.但你不得不停下来.
值得一提的是,"一旦找到第一场比赛,范围查找就会停止".
这种"范围级别"的概念解释了为什么"这个"可以用新创建的范围进行更改,如果它是在嵌套函数中查找的话.这是一个链接,它涉及所有这些细节,你想知道的关于javascript范围的一切
全局变量与全球明星(成龙,纳尔逊曼德拉)完全一样.您可以从应用程序的任何部分访问它们(获取或设置值).全球活动就像全球活动(新年,圣诞节).您可以从应用程序的任何部分执行(调用)它们.
//global variable
var a = 2;
//global function
function b(){
console.log(a); //access global variable
}
Run Code Online (Sandbox Code Playgroud)
如果你在美国,你可能会认识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
Run Code Online (Sandbox Code Playgroud)
运行代码.希望这会给出一个关于范围界定的想法
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);
Run Code Online (Sandbox Code Playgroud)
小智 6
试试这个奇怪的例子。在下面的示例中,如果 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());
Run Code Online (Sandbox Code Playgroud)
小智 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
Run Code Online (Sandbox Code Playgroud)
使用函数代替:
var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10
Run Code Online (Sandbox Code Playgroud)
在第一个示例中,没有块作用域,因此最初声明的变量被覆盖.在第二个示例中,由于函数有一个新的作用域,因此最初声明的变量是SHADOWED,而不是被覆盖.
除了以下内容之外,您几乎只需要知道JavaScript范围;
所以你可以看到JavaScript范围实际上非常简单,尽管并不总是直观的.有几点需要注意:
所以这段代码:
var i = 1;
function abc() {
i = 2;
var i = 3;
}
console.log(i); // outputs 1
Run Code Online (Sandbox Code Playgroud)
相当于:
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);
Run Code Online (Sandbox Code Playgroud)
这似乎与直觉相反,但从命令式语言设计者的角度来看,这是有道理的.
const'和' let'您应该为您创建的每个变量使用块作用域,就像大多数其他主要语言一样.var已经过时了.这使您的代码更安全,更易于维护.
const应该用于95%的病例.它使得变量引用无法改变.数组,对象和DOM节点属性可以更改,应该可能更改const.
let应该用于任何期望被重新分配的变量.这包括在for循环中.如果您在初始化之后更改了值,请使用let.
块范围意味着变量仅在声明它的括号内可用.这扩展到内部范围,包括在范围内创建的匿名函数.
前端编码人员经常遇到的一个尚未描述的非常常见的问题是 HTML 中的内联事件处理程序可见的范围 - 例如,
<button onclick="foo()"></button>
Run Code Online (Sandbox Code Playgroud)
on*属性可以引用的变量的范围必须是:
querySelector作为独立变量将指向document.querySelector; 罕见)否则,您将在调用处理程序时收到 ReferenceError。因此,例如,如果内联处理程序引用在 window.onloador内部定义的函数,则$(function() {引用将失败,因为内联处理程序可能只引用全局范围内的变量,而该函数不是全局的:
window.addEventListener('DOMContentLoaded', () => {
function foo() {
console.log('foo running');
}
});Run Code Online (Sandbox Code Playgroud)
<button onclick="foo()">click</button>Run Code Online (Sandbox Code Playgroud)
document处理程序附加到的元素的属性和属性也可以作为内联处理程序内的独立变量引用,因为内联处理程序在两个with块内调用,一个用于document,一个用于元素。这些处理程序中变量的作用域链非常不直观,一个工作的事件处理程序可能需要一个函数是全局的(并且应该避免不必要的全局污染)。
由于内联处理程序中的作用域链非常奇怪,而且由于内联处理程序需要全局污染才能工作,而且由于内联处理程序有时在传递参数时需要丑陋的字符串转义,因此避免它们可能更容易。相反,使用 Javascript 附加事件处理程序(如 with addEventListener),而不是使用 HTML 标记。
function foo() {
console.log('foo running');
}
document.querySelector('.my-button').addEventListener('click', foo);Run Code Online (Sandbox Code Playgroud)
<button class="my-button">click</button>Run Code Online (Sandbox Code Playgroud)
<script type="module">)另一方面,与<script>运行在顶层的普通标签不同,ES6 模块内的代码在其自己的私有范围内运行。在普通<script>标签顶部定义的变量是全局变量,因此您可以在其他<script>标签中引用它,如下所示:
<script>
const foo = 'foo';
</script>
<script>
console.log(foo);
</script>Run Code Online (Sandbox Code Playgroud)
但是 ES6 模块的顶层不是全局的。在 ES6 模块顶部声明的变量只会在该模块内部可见,除非该变量被显式exported,或者除非它被分配给全局对象的属性。
<script type="module">
const foo = 'foo';
</script>
<script>
// Can't access foo here, because the other script is a module
console.log(typeof foo);
</script>Run Code Online (Sandbox Code Playgroud)
ES6 模块的顶层类似于普通<script>. 模块可以引用任何全局变量,除非模块是为它明确设计的,否则没有任何东西可以引用模块内部的任何东西。