javascript - window全局变量 - js闭包




JavaScript中变量的范围是什么? (17)

javascript中变量的范围是什么? 他们有相同的范围内,而不是功能外? 或者它甚至很重要? 另外,如果变量是全局定义的,则变量存储在哪里?


现代Js,ES6 +,' const '和' let '

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

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

let应该用于任何期望被重新分配的变量。 这包括在for循环中。 如果您在初始化之后更改了值,请使用let

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


老派的JavaScript

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

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

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

现代JavaScript

最新的JavaScript规范现在还允许第三个范围:

  1. 块范围 :变量在声明之后的块内已知(**)

我如何创建块范围变量?

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

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及以下版本(不支持)
    • 歌剧36及以下(部分支持)
    • Chome 51及以下(部分支持)

如何跟踪浏览器支持

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

(*)由于JavaScript变量被hoisted所以全局和功能范围的变量可以在声明之前被初始化和使用。 这意味着声明总是在范围的顶部。

(**)块范围变量不会被挂起


1)有一个全局范围,一个函数范围,以及with和catch范围。 一般情况下,变量没有“块”级作用域 - with和catch语句将名称添加到它们的块中。

2)范围由函数嵌套到全局范围。

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

编辑:ECMAAScript 6(和谐)被指定支持让我,我知道铬允许一个'和谐'的标志,所以也许它支持它..

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

编辑:基于Benjamin指出的评论中的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,除非它是 - 它是的属性。

哎呀! 小心使用'with' - 就像var是一个noop,如果该变量已经在函数中定义,它也是一个noop关于从对象导入的名字! 对已定义的名字有点头脑会让这个安全得多。 因为这个,我个人绝对不会使用。


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使用范围链为给定函数建立范围。 通常有一个全局范围,每个定义的函数都有自己的嵌套范围。 在另一个函数中定义的任何函数都有一个与外部函数链接的局部范围。 始终是定义范围的来源中的位置。

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

解析变量时,JavaScript从最内层的范围开始并向外搜索。


ECMAScript 6 introduced the let and const keywords. These keywords can be used in place of the var keyword. Contrary to the var keyword, the let and const keywords support the declaration of local scope inside block statements.

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

In EcmaScript5, there are mainly two scopes, local scope and global scope but in EcmaScript6 we have mainly three scopes, local scope, global scope and a new scope called block scope .

Example of block scope is :-

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

In JavaScript there are two types of scope:

  • Local scope
  • 全球范围

The Below function has a local scope variable carName . And this variable is not accessible from outside of the function.

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

The Below Class has a Global scope variable carName . And this variable is accessible from everywhere in the class.

class {

    var carName = " Volvo";

    // code here can use carName

    function myFunction() {
        alert(carName);
        // code here can use carName 
    }
}

There are two types of scopes in JavaScript.

  1. Global scope : variable which is announced in global scope can be used anywhere in the program very smoothly. 例如:

    var carName = " BMW";
    
    // code here can use carName
    
    function myFunction() {
         // code here can use carName 
    }
    
  2. Functional scope or Local scope : variable declared in this scope can be used in its own function only. 例如:

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

Try this curious example. In the example below if a were a numeric initialized at 0, you'd see 0 and then 1. Except a is an object and javascript will pass f1 a pointer of a rather than a copy of it. The result is that you get the same alert both times.

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

全局声明的变量具有全局范围。 在一个函数中声明的变量的范围是该函数的范围,并且映射同名的全局变量。

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


几乎只有两种类型的JavaScript范围:

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

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

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

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


我发现很多JavaScript新手无法理解默认情况下继承是可用的,到目前为止函数范围是唯一的范围。 我为去年年底撰写的名为JSPretty的美化工提供了一个扩展。 代码中的特征颜色函数作用域始终将颜色与该范围内声明的所有变量相关联。 当一个范围内的颜色变量在不同的范围内使用时,可视化演示关闭。

尝试使用以下功能:

在以下网址查看演示:

查看代码:

目前该功能支持16个嵌套函数的深度,但目前不会为全局变量着色。


我想我能做的最好的就是给你一些学习的例子。 Javascript程序员的实际排名依据他们对于范围的理解程度。 它有时可能是非常直观的。

  1. 一个全局范围的变量

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

    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. 捕获子句范围的变量

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

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


最初由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的词汇环境,并且是被调用时在警报中引用的值。


每一段JavaScript代码(全局代码或函数)都有一个与其关联的作用域链。 这个作用域链是一个对象的列表或链,它定义了那个代码的“范围内”的变量。 当JavaScript需要查找变量x (称为可变分辨率的过程)的值时,它首先查看链中的第一个对象。 如果该对象具有名为x的属性,则使用该属性的值。 如果第一个对象没有名为x的属性,则JavaScript继续使用链中的下一个对象进行搜索。 如果第二个对象没有名为x的属性,则搜索将继续到下一个对象,依此类推。 如果x不是作用域链中任何对象的属性,则x不在该代码的作用域中,并且会发生ReferenceError。 在顶级JavaScript代码中(即,不包含在任何函数定义中的代码),范围链由单个对象即全局对象组成。 在非嵌套函数中,作用域链由两个对象组成。 第一个是定义函数参数和局部变量的对象,第二个是全局对象。 在嵌套函数中,作用域链包含三个或更多对象。 了解如何创建这个对象链是很重要的。 当一个函数是DEFINED时 ,它将存储范围链然后生效。 当该函数为INVOKED时 ,它创建一个新对象来存储其局部变量,并将该新对象添加到存储的作用域链中,以创建一个代表该函数调用作用域的新的更长的链。 对于嵌套函数,这会变得更有趣,因为每次调用外部函数时,都会再次定义内部函数。 Since the scope chain differs on each invocation of the outer function, the inner function will be subtly different each time it is defined—the code of the inner function will be identical on each invocation of the outer function, but the scope chain associated with that code will be different . This notion of a scope chain is crucial for understanding closures .


这是一个例子:

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

您需要调查关闭,以及如何使用它们来创建私人成员







scope