javascript中的变量范围是什么?它们的内部是否与函数外部相同?或者甚至重要吗?另外,如果变量是全局定义的,那么它们存储在哪里?
我认为我能做的最好的事情就是给你一些学习的例子.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 one() { if (true) { const a = 4; } alert(a); // alerts '1' because the 'const' keyword also uses block scoping as 'let' }
高级:关闭
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'
这将打印出undefined
和10
而不是5
和10
自JavaScript的始终移动变量声明(未初始化)的范围的顶部,使得代码等同于:
var x = 5; (function () { console.log(x); var x = 10; console.log(x); })();
Catch子句范围的变量
var x = 5; (function () { var x; console.log(x); x = 10; console.log(x); })();
这将打印出来5
,6
,5
.在catch子句中e
隐藏全局变量和局部变量.但是这个特殊范围仅适用于捕获的变量.如果你var f;
在catch子句中写入,那么它就像你在try-catch块之前或之后定义它一样.
Javascript使用范围链来确定给定函数的范围.通常有一个全局范围,每个定义的函数都有自己的嵌套范围.在另一个函数中定义的任何函数都有一个链接到外部函数的局部作用域.始终是源中定义范围的位置.
范围链中的元素基本上是一个带有指向其父范围的指针的Map.
解析变量时,javascript从最里面的范围开始并向外搜索.
全球宣布的变量具有全球范围.在函数内声明的变量作用于该函数,并且阴影全局变量具有相同名称.
(我确信真正的JavaScript程序员可以在其他答案中指出很多细微之处.特别是我在这个页面上看到了this
任何时候究竟意味着什么.希望这个更多的介绍性链接足以让你开始.)
传统上,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
只在第一个for循环中被知道,而不是在之前和之后.然而,我们的变量i
在整个函数中是已知的.
另外,请考虑块范围变量在声明之前是未知的,因为它们不会被提升.您也不允许在同一块中重新声明相同的块范围变量.这使得块范围变量比全局或功能范围变量更不容易出错,这些变量被提升并且在多个声明的情况下不会产生任何错误.
今天是否安全使用取决于您的环境:
如果您正在编写服务器端JavaScript代码(Node.js),则可以安全地使用该let
语句.
如果您正在编写客户端JavaScript代码并使用转换器(如Traceur),则可以安全地使用该let
语句,但是您的代码在性能方面可能不是最优的.
如果您正在编写客户端JavaScript代码而不使用转换器,则需要考虑浏览器支持.
今天,2016年2月23日,这些是一些不支持let
或只支持部分支持的浏览器:
Internet Explorer 10及以下版本(不支持)
Firefox 43及以下版本(不支持)
Safari 9及以下版本(不支持)
Opera Mini 8及以下版本(不支持)
Android浏览器4及以下(不支持)
Opera 36及以下(部分支持)
Chome 51及以下(部分支持)
有关let
在阅读本答案时哪些浏览器支持该语句的最新概述,请参阅此let
页面.
(*)全局和功能范围的变量可以在声明之前初始化和使用,因为JavaScript变量是悬挂的.这意味着声明始终位于范围的顶部.
(**)未提升块范围变量
这是一个例子:
您将要研究闭包,以及如何使用它们来创建私有成员.
根据我的理解,关键是Javascript具有功能级别范围与更常见的C块范围.
这是一篇关于这个主题的好文章.
在"Javascript 1.7"(Mozilla对Javascript的扩展)中,还可以使用let
语句声明块范围变量:
var a = 4; let (a = 3) { alert(a); // 3 } alert(a); // 4
最初由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的环境,这是调用时警报中引用的值.
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!对已经定义的名称进行一点点提醒会使这更加安全.因为这个原因,我个人永远不会使用.
我发现许多刚接触JavaScript的人很难理解默认情况下语言中的继承是可用的,到目前为止,函数范围是唯一的范围.我为去年年底写的一个名为JSPretty的美化家提供了扩展.功能颜色代码中的函数范围,并始终将颜色与该范围中声明的所有变量相关联.当在不同范围内使用具有来自一个范围的颜色的变量时,可视地演示闭合.
尝试以下功能:
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.js
https://github.com/austincheney/Pretty-Diff/blob/master/lib/jspretty.js
目前,该功能支持深度为16的嵌套函数,但目前不对全局变量着色.
JavaScript只有两种类型的范围:
全局范围:全局只不过是一个窗口级别的范围.这里,整个应用程序中存在变量.
功能范围:在具有var
关键字的函数内声明的变量具有功能范围.
无论何时调用函数,都会创建一个变量范围对象(并包含在范围链中),后面跟着JavaScript中的变量.
a = "global"; function outer(){ b = "local"; console.log(a+b); //"globallocal" } outer();
范围链 - >
窗口级别 - a
和outer
功能在范围链中处于顶层.
当外部函数调用new 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 }
如果你在美国,你可能会认识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
查看本文以深入了解范围
运行代码.希望这会给出一个关于范围界定的想法
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);
ALMOST只有两种类型的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-clause显然是另一个例外,但是使用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);
这似乎与直觉相反,但从命令式语言设计者的角度来看,这是有道理的.
const
'和' let
'
您应该为您创建的每个变量使用块作用域,就像大多数其他主要语言一样.var
已经过时了.这使您的代码更安全,更易于维护.
const
应该用于95%的病例.它使得变量引用无法改变.数组,对象和DOM节点属性可以更改,应该可能更改const
.
let
应该用于任何期望被重新分配的变量.这包括在for循环中.如果您在初始化之后更改了值,请使用let
.
块范围意味着变量仅在声明它的括号内可用.这扩展到内部范围,包括在范围内创建的匿名函数.