JavaScript中的变量范围是什么?

时间:2009-02-01 08:27:20

标签: javascript function variables scope var

javascript中的变量范围是什么?它们的内部是否与函数外部相同?或者甚至重要吗?另外,如果变量是全局定义的,那么它们存储在哪里?

27 个答案:

答案 0 :(得分:2408)

我认为我能做的最好的事情就是给你一些学习的例子。 Javascript程序员实际上根据他们理解范围的程度来排名。 它有时可能非常违反直觉。

  1. 全局范围的变量

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

    // 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'
    }
    
  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. 高级关闭

    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. 高级基于原型的范围解析

    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始终将变量声明(不是初始化)移动到范围的顶部,代码相当于:

    var x = 5;
    
    (function () {
        var x;
        console.log(x);
        x = 10;
        console.log(x); 
    })();
    
  8. Catch子句范围的变量

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

    这将打印出565。 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

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

  1. 全局范围:从应用程序开始(*)
  2. ,整个应用程序都知道变量
  3. 功能范围:从函数开头(*)
  4. 中声明变量the function中的变量

    我不会详细说明这一点,因为已经有许多其他答案解释了这一点。

    现代JavaScript

    most recent JavaScript specs现在还允许第三个范围:

    1. 阻止范围:变量在the block中已知,从声明之后起,(**)
    2. 如何创建块范围变量?

      传统上,您可以像这样创建变量:

      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代码并使用基于浏览器的转换器(如Traceurbabel-standalone),则可以安全地使用let语句但是,就性能而言,您的代码可能不是最优的。

      • 如果您正在编写客户端JavaScript代码并使用基于节点的转换器(例如traceur shell scriptBabel),则可以安全地使用let声明。而且因为您的浏览器只会知道转换后的代码,所以应该限制性能缺陷。

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

        这些浏览器根本不支持let

        • Internet Explorer 10 及以下
        • Firefox 43 及以下
        • Safari 9 及以下
        • Android浏览器4 及以下
        • Opera 27 及以下
        • Chome 40 及以下
        • 任何版本的 Opera Mini &amp; Blackberry浏览器

      enter image description here

      如何跟踪浏览器支持

      有关哪些浏览器在您阅读此答案时支持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块范围。

Here is a good article on the subject.

答案 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的词法环境,并且是调用时警报中引用的值。

enter image description here

答案 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只有两种范围:

  1. 全局范围:全局只是一个窗口级别的范围。这里,整个应用程序中存在变量。
  2. 功能范围:在具有var关键字的函数中声明的变量具有功能范围。
  3. 每当调用一个函数时,都会创建一个变量范围对象(并包含在范围链中),后面跟着JavaScript中的变量。

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

    范围链 - &gt;

    1. 窗口级别 - aouter功能位于范围链的顶层。
    2. 当外部函数称为新variable scope object(并包含在作用域链中)时,其中添加了变量b
    3. 现在当一个变量a需要它时,它首先搜索最近的变量范围,如果变量不存在,那么它将移动到变量范围链的下一个对象。在这种情况下,它是窗口级别。

答案 11 :(得分:9)

只是为了添加其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则,以确定当前执行的代码如何访问它们。该查找可以用于分配给变量的目的,该变量是LHS(左手侧)参考,或者它可以用于检索其值,即RHS(右手侧)参考。这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作。

所以从这个角度来看,我认为一张图片可以帮助我在Kyle Simpson的Scopes and Closures电子书中找到:

image

引用他的电子书:

  

该建筑代表我们程序的嵌套范围规则集。首先   建筑物的楼层代表您当前执行的范围,   无论你在哪。建筑的顶层是全球范围。   您可以通过查看当前楼层来解决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    

Check this article for in-depth understanding of scope

答案 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范围:

  • 每个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范围

所以你可以看到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);

这看似违反直觉,但从命令式语言设计师的角度来看,这是有道理的。

答案 15 :(得分:5)

Modern Js,ES6 +,'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关键字声明的每个变量都作用于该函数。但是,如果在该函数中声明了其他函数,则这些函数将有权访问外部函数的变量。这称为作用域链。它以以下方式工作:

  1. 当函数试图解析变量值时,它首先会查看其自身的作用域。这是函数主体,即大括号{}之间的所有内容(其他函数中的变量均在此范围内)。
  2. 如果找不到函数体内的变量,它将爬到链上,并在定义函数的地方查看函数中的变量范围 。这就是词汇作用域的含义,我们可以在代码中看到定义此函数的位置,因此仅查看代码即可确定作用域链。

示例:

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

当我们尝试将变量foobarfoobar记录到控制台时会发生以下情况:

  1. 我们尝试将foo登录到控制台,可以在函数innerFunc本身中找到foo。因此,foo的值将解析为字符串innerFunc
  2. 我们尝试将bar登录到控制台,在函数innerFunc本身内部找不到bar。因此,我们需要攀登范围链。我们首先查看定义了函数innerFunc的外部函数。这是功能outerFunc。在outerFunc的范围内,我们可以找到变量bar,其中包含字符串'outerFunc'。
  3. foobar在innerFunc中找不到。 。因此,我们需要爬升作用域链到innerFunc作用域。在这里也找不到它,我们将爬升到全局范围(即最外部的范围)的另一层。我们在这里找到变量foobar,其中包含字符串“ global”。如果在攀登范围链之后找不到变量,则JS引擎将抛出 referenceError

ES6(ES 2015)及更低版本:

词汇范围和作用域链的相同概念仍然适用于ES6。但是,引入了一种声明变量的新方法。有以下内容:

  • let:创建一个块范围变量
  • const:创建一个块范围的变量,该变量必须初始化且不能重新分配

varlet / 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 blocksdocument ,一个用于元素。这些处理程序中变量的作用域链为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收集并维护所有已声明的标识符(变量)的查找列表,并针对当前执行的代码如何访问使用严格的规则。

范围是用于通过变量标识符名称查找变量的一组规则。

  • 如果在直接作用域中找不到变量,则Engine会查询下一个外部包含作用域,一直持续到找到该对象或到达最外部(也就是全局)作用域为止。
  • 是一组规则,用于确定在何处以及如何查找变量(标识符)。该查找可能是为了分配给变量,它是一个LHS(左侧)引用,或者可能是为了检索其值,它是一个RHS(右侧)引用。 。
  • LHS引用来自分配操作。范围相关的分配可以通过=运算符或通过将参数传递给(分配给)函数参数来实现。
  • JavaScript引擎首先在执行代码之前对其进行编译,然后将其分解为var a = 2之类的语句;分为两个单独的步骤:1。首先,var a在该范围内声明它。这是在开始执行代码之前执行的。 2号后来,a = 2查找变量(LHS参考),并在找到该变量时将其赋值。
  • LHS和RHS引用查找均从当前执行的范围开始,并且如果需要(即,他们在那里找不到所需的内容),它们将沿嵌套范围向上工作,一个一次(查找)范围,寻找标识符,直到它们到达全局(顶层)并停下来,要么找到它,要么不找到它。未实现的RHS引用会导致引发ReferenceError。未实现的LHS引用会导致自动创建该名称的隐式全局自动创建(如果不是在严格模式下),或者是ReferenceError(如果在严格模式下)。
  • 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中有两种类型的范围。

  1. 全球范围:在全球范围内公布的变量可以非常顺利地在程序中的任何位置使用。例如:

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

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