JavaScript中variables的范围是什么?

什么是JavaScript的variables的范围? 他们有内部相同的范围,而不是外部function? 还是它甚至重要? 另外,如果全局定义variables存储在哪里?

我想我能做的最好的是给你一堆例子来学习。 Javascript程序员实际上是按照他们理解范围的程度排名的。 它有时可能是非常直观的。

  1. 一个全局范围的variables

    // global scope var a = 1; function one() { alert(a); // alerts '1' } 
  2. 本地范围

     // global scope var a = 1; function two(a) { // 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' } 
  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 } 
  4. 中级对象属性

     var a = 1; function Five() { this.a = 5; } alert(new Five().a); // alerts '5' 
  5. 高级closures

     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' }; })(); 
  6. 高级基于原型的范围parsing

     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' 

  7. 全球+本地一个额外的复杂案例

     var x = 5; (function () { console.log(x); var x = 10; console.log(x); })(); 

    这将打印出undefined10而不是510因为JavaScript总是将variables声明(不是初始化)移动到范围的顶部,使得代码等同于:

     var x = 5; (function () { var x; console.log(x); x = 10; console.log(x); })(); 
  8. 捕获子句范围的variables

     var e = 5; console.log(e); try { throw 6; } catch (e) { console.log(e); } console.log(e); 

    这将打印出5 。 在catch子句里面e阴影全局variables和局部variables。 但是这个特殊的范围只适用于被捕获的variables。 如果你写var f; 在catch子句内部,就和在try-catch块之前或之后定义它一样。

Javascript使用范围链为给定函数build立范围。 通常有一个全局范围,每个定义的函数都有自己的嵌套范围。 在另一个函数中定义的任何函数都有一个与外部函数链接的局部范围。 始终是定义范围的源代码中的位置。

作用域链中的元素基本上是一个带有指向其父作用域的指针的Map。

当parsing一个variables时,JavaScript从最里面的范围开始向外search。

全局声明的variables具有全局范围。 在一个函数中声明的variables被作用于该函数,并映射同名的全局variables。

(我相信真正的JavaScript程序员可以在其他答案中指出很多细节,特别是我在任何时候都会看到这个页面的意思,希望这个更具启发性的链接足以让你开始虽然。)

老派的JavaScript

传统上,JavaScript实际上只有两种types的范围:

  1. 全球范围 :variables从申报的那一刻起就在整个申请中都是已知的
  2. function范围 :variables在它们被声明的函数内是已知

我不会详细说明这一点,因为已经有很多其他解释这个区别的答案。


现代JavaScript

最近的JavaScript规范现在也允许第三个范围:

  1. 块范围 :variables在声明之后的块内是已知

我如何创build块范围variables?

传统上,你可以像这样创build你的variables:

 var myVariable = "Some text"; 

块范围variables是这样创build的:

 let myVariable = "Some text"; 

那么function范围和块范围有什么区别呢?

要理解function范围和块范围之间的区别,请考虑以下代码:

 // i IS NOT known here // j IS NOT known here function loop(arr) { // i IS NOT known here // j IS NOT known here for( var i = 0; i < arr.length; i++ ) { // i IS known here // j IS NOT known here }; // i IS known here // j IS NOT known here for( let j = 0; j < arr.length; j++ ) { // i IS known here // j IS known here }; // i IS known here // j IS NOT known here } // i IS NOT known here // j IS NOT known here 

在这里,我们可以看到我们的variablesj只在第一个for循环中被知道,而不是在之前和之后。 然而,我们的variablesi从它被定义的那一刻起就被称为整个函数。


今天使用块范围variables是否安全?

今天是否安全使用取决于您的环境:

  • 如果您正在编写服务器端JavaScript代码( Node.js ),则可以安全地使用let语句。

  • 如果您正在编写客户端JavaScript代码并使用转译器(如Traceur ),则可以安全地使用let语句,但是您的代码在性能方面可能不是最佳的。

  • 如果您正在编写客户端JavaScript代码并且不使用转译器,则需要考虑浏览器支持。

    今天,2016年2月23日,这些是一些浏览器,不支持let或只有部分支持:

    • Internet Explorer 10及以下版本(不支持)
    • Firefox 43及以下版本(不支持)
    • Safari 9及以下版本(不支持)
    • Opera Mini 8及以下版本(不支持)
    • Android浏览器4及以下(不支持)
    • 歌剧36及以下(部分支持)
    • Chome 51及以下(部分支持)

在这里输入图像描述


如何跟踪浏览器的支持

有关阅读本答案时哪些浏览器支持let语句的最新概述,请参阅Can I Use页面

这是一个例子:

 <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> 

你会想调查closures,以及如何使用它们来build立私人成员 。

据我所知,关键在于Javascript具有function级别范围,而不是更常见的C块范围。

这是一个关于这个问题的好文章。

在“Javascript 1.7”(Mozilla的JavaScript扩展)中,还可以用let语句声明块范围variables:

  var a = 4; let (a = 3) { alert(a); // 3 } alert(a); // 4 

最初由Brendan Eichdevise的JavaScript范围的想法来自于HyperCard脚本语言HyperTalk 。

在这种语言下,显示器完成类似于一堆索引卡片。 有一张主卡被称为背景。 这是透明的,可以看作是底部的卡。 此基卡上的任何内容都与放在其上的卡共享。 每张放在顶部的卡片都有自己的内容,优先于以前的卡片,但是如果需要的话,仍然可以使用现有的卡片。

JavaScript正是如此devise的。 它只是有不同的名字。 JavaScript中的卡被称为执行上下文ECMA 。 这些背景中的每一个都包含三个主要部分。 一个可变的环境,一个词汇环境和一个这个绑定。 回到卡参考,词法环境包含来自先前纸牌中的较低纸牌的所有内容。 当前的上下文位于堆栈的顶部,任何声明的内容都将存储在variables环境中。 在命名冲突的情况下,variables环境将优先。

这个绑定将指向包含的对象。 有时,范围或执行上下文在包含对象更改的情况下会发生更改,例如在包含对象可能是window或构造函数的声明函数中。

这些执行上下文是在控制权被转移的时候创build的。 当代码开始执行时,控制权被转移,而这主要是由函数执行完成的。

这就是技术上的解释。 实际上,在JavaScript中记住这一点很重要

  • 范围在技术上是“执行上下文”
  • 上下文形成了一个存储variables的环境
  • 堆栈顶部优先(底部是全局上下文)
  • 每个函数创build一个执行上下文(但并不总是一个新的绑定)

将此应用于此页面上的其中一个示例(5.“Closure”),可以遵循执行上下文的堆栈。 在这个例子中,堆栈中有三个上下文。 它们由外部上下文,由var 6调用的立即调用的函数中的上下文以及在var 6的立即调用的函数内返回的函数中的上下文定义。

i )外部环境。 它具有a = 1的可变环境
ii )IIFE上下文,它具有a = 1的词汇环境,但是a = 6的variables环境优先于堆栈
iii )返回的函数上下文,它具有a = 6的词汇环境,并且是被调用时在警报中引用的值。

在这里输入图像描述

1)有一个全球范围,一个function范围,以及与范围。 一般情况下,variables没有“块”级作用域 – with和catch语句将名称添加到块中。

2)范围一直嵌套到全局范围的函数中。

3)通过原型链来解决属性问题。 with语句将对象属性名称带入由with块定义的词法范围。

编辑:ECMAAScript 6(和谐)是specied支持让,我知道铬允许一个“和谐”的标志,所以也许它支持它..

让我们来支持块级的范围,但是你必须使用关键字来实现。

编辑:基于本杰明指出的意见,并在发表声明,我已经编辑的post,并添加更多。 with和catch语句都将variables引入到它们各自的块中,这块范围。 这些variables被别名传递给它们的对象的属性。

  //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'在上面的词法上下文(函数或全局)中创build一个新的variablestest1,除非它是一个 – 它是的属性。

哎呀! 小心使用'with' – 就像var是一个noop(如果variables已经在函数中定义的话),对于从对象导入的名字也是一个noop! 有一个名字已经定义的名字会使这更安全。 因为这个,我个人绝对不会用。

我发现很多JavaScript新手无法理解默认情况下inheritance是可用的,而函数范围是目前唯一的范围。 我在去年年底写了一个叫做JSPretty的美化工具。 代码中的特征颜色函数作用域始终将颜色与该范围内声明的所有variables相关联。 当一个范围的颜色variables在不同的范围内使用时,可视化展示闭包。

尝试以下function:

在以下url查看演示:

查看代码:

目前该function提供了对16个嵌套函数深度的支持,但是当前不会为全局variables着色。

JavaScript只有两种types的范围:

  1. 全球范围 :全球无非是一个窗口级别的范围。在这里,variables在整个应用程序中呈现。
  2. function范围 :在具有var关键字的函数内声明的variables具有function范围。

每当一个函数被调用时,就会创build一个variables作用域对象(包含在作用域链中),然后在JavaScript中使用variables。

  a = "global"; function outer(){ b = "local"; console.log(a+b); //"globallocal" } outer(); 

范围链 – >

  1. 窗口级别 – aouter函数在范围链中处于顶层。
  2. 当外层函数调用一个新的variable scope object (并包含在作用域链中)时,在其中添加了variablesb

现在,当一个variables需要时,它首先search最近的variables作用域,如果variables不存在,则它移动到variables作用域链中的下一个对象,在这种情况下,它是窗口级别。

运行代码。 希望这会给出一个关于范围的概念

 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); 

为了增加其他答案,范围是所有声明的标识符(variables)的查找列表,并强制执行一组严格的规则,以便如何访问当前正在执行的代码。 这种查询可能是为了分配给LHS(左侧)参考的variables,也可能是为了检索它的值,这是RHS(右侧)参考。 这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作。

所以从这个angular度来看,我认为这张照片有助于我在凯尔·辛普森的范围和闭幕电子书中find的:

图片

引用他的电子书:

该build筑物代表我们的程序的嵌套作用域规则集。 无论您身在何处,大楼的一楼都代表您正在执行的范围。 build筑物的顶层是全球范围。 您通过查看当前楼层来解决LHS和RHS参考,如果找不到,请将电梯送到下一层,然后再看下一层,依此类推。 一旦你到达顶层(全球范围),你要么find你要找的东西,要么你没有find。 但是你不得不停下来。

值得一提的是,“范围查找一旦find第一个匹配就停止”。

这个“范围级别”的概念解释了为什么可以用新创build的范围来更改“this”,如果在嵌套函数中查找它。 这是一个链接,进入所有这些细节, 你想了解的JavaScript范围的一切

全球范围:

全球variables和全球明星一样(成龙,纳尔逊·曼德拉)。 您可以从应用程序的任何部分访问它们(获取或设置值)。 全球职能就像全球事件(新年,圣诞节)。 您可以从应用程序的任何部分执行(调用)它们。

 //global variable var a = 2; //global function function b(){ console.log(a); //access global variable } 

当地范围:

如果你在美国,你可能会知道金·卡戴珊(Kim Kardashian),臭名昭着的名人(她设法制作小报)。 但美国以外的人不会承认她。 她是一个地方明星,绑在她的领土上。

局部variables就像当地的明星。 您只能在范围内访问它们(获取或设置值)。 一个本地函数就像本地事件 – 你可以在该范围内执行(庆祝)。 如果你想从范围之外访问它们,你会得到一个参考错误

 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 

查看这篇文章,以深入了解范围

几乎只有两种types的JavaScript范围:

  • 每个var声明的范围都与最直接包含的函数相关联
  • 如果var声明没有包含函数,那么它是全局作用域

所以,函数以外的任何块都不会创build新的作用域。 这就解释了为什么for-loops会覆盖外部的scopedvariables:

 var i = 10, v = 10; for (var i = 0; i < 5; i++) { var v = 5; } console.log(i, v); // output 5 5 

改用function:

 var i = 10, v = 10; $.each([0, 1, 2, 3, 4], function(i) { var v = 5; }); console.log(i,v); // output 10 10 

在第一个例子中,没有块范围,所以最初声明的variables被覆盖。 在第二个例子中,由于这个函数有一个新的范围,所以最初声明的variables是SHADOWED,而不是被覆盖。

这几乎是所有你需要知道的JavaScript范围,除了:

所以你可以看到JavaScript范围确实非常简单,尽pipe并不总是直观的。 有几件事要注意:

  • 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); 

这看起来可能违反直觉,但从命令式语言devise者的angular度来看,这是有道理的。

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); 

每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链。 这个作用域链是一个对象的列表或链,它定义了这个代码的“范围内”的variables。 当JavaScript需要查找variablesx (称为可变分辨率的过程)的值时,它首先查看链中的第一个对象。 如果该对象具有名为x的属性,则使用该属性的值。 如果第一个对象没有名为x的属性,则JavaScript继续使用链中的下一个对象进行search。 如果第二个对象不具有名为x的属性,则search将移至下一个对象,依此类推。 如果x不是作用域链中任何对象的属性,则x不在该代码的作用域中,并且发生ReferenceError。 在顶级JavaScript代码中(即,不包含在任何函数定义中的代码),作用域链由单个对象(全局对象)组成。 在非嵌套函数中,作用域链由两个对象组成。 第一个是定义函数参数和局部variables的对象,第二个是全局对象。 在嵌套函数中,作用域链包含三个或更多对象。 了解如何创build这个链条是很重要的。 当一个函数是DEFINED时 ,它将存储范围链然后生效。 当这个函数被INVOKED时 ,它创build一个新的对象来存储它的局部variables,并且将这个新的对象添加到存储的作用域链中来创build一个新的,更长的链,它代表了该函数调用的作用域。 对于嵌套函数,这会变得更有趣,因为每次调用外部函数时,都会再次定义内部函数。 由于范围链在外部函数的每次调用时都不相同因此内部函数在每次定义时都会有细微的差别 – 每次调用外部函数时内部函数的代码都是相同的,但是与该函数关联的作用域链代码会有所不同 。 范围链的这个概念对于理解闭包至关重要。

现代Js,ES6 +,“ const ”和“ let

您应该为每个创build的variables使用块范围,就像大多数其他主要语言一样。 var已经过时了 。 这使您的代码更安全,更易于维护。

95%的情况下应该使用const 。 这使得variables引用不能改变。 数组,对象和DOM节点属性可以改变,应该可能是const

应该用于任何期望被重新分配的variables。 这包括在for循环中。 如果您在初始化之后编写varName = ,请使用let

块范围意味着该variables只能在声明的括号内可用。 这扩展到内部作用域,包括范围内创build的匿名函数。

试试这个好奇的例子。 在下面的例子中,如果a是一个初始化为0的数字,你会看到0,然后是1.除了是一个对象,JavaScript会传递一个指针而不是一个副本。 结果是两次都得到相同的警报。

 var a = new Date(); function f1(b) { b.setDate(b.getDate()+1); alert(b.getDate()); } f1(a); alert(a.getDate()); 

我的理解是,有3个范围:全球范围,全球可用; 本地范围,可用于整个function,不pipe块; 和块范围,只适用于块,语句或使用它的expression式。 全局和局部作用域用关键字“var”表示,可以在函数内部或外部使用,关键字'let'表示块作用域。

对于那些认为只有全局和局部范围的人来说,请解释为什么Mozilla会有一整页来描述JS中块范围的细微差别。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

在JavaScript中有两种types的范围:

  • 本地范围
  • 全球范围

下面的函数有一个本地作用域variablescarName 。 这个variables不能从函数外部访问。

 function myFunction() { var carName = "Volvo"; alert(carName); // code here can use carName } 

下面的类有一个全局范围variablescarName 。 这个variables可以从课程中的任何地方访问。

 class { var carName = " Volvo"; // code here can use carName function myFunction() { alert(carName); // code here can use carName } } 

JavaScript中有两种types的作用域。

  1. 全局范围 :在全局范围内公布的variables可以非常顺利地在程序中的任何地方使用。 例如:

     var carName = " BMW"; // code here can use carName function myFunction() { // code here can use carName } 
  2. function范围或本地范围 :在此范围内声明的variables只能在其自己的function中使用。 例如:

     // code here can not use carName function myFunction() { var carName = "BMW"; // code here can use carName } 

全局:在函数之外声明的variables

Local:在函数内声明的variables,只能在该范围内调用