blog icon indicating copy to clipboard operation
blog copied to clipboard

ES6之变量和作用域

Open jiayisheji opened this issue 4 years ago • 0 comments

变量

变量是具有唯一名称的命名容器,用于存储数据值。 以下语句声明了一个名称为“name”的变量:

let name;

console.log(name); // undefined

在JavaScript中,变量在创建时被初始化为undefined。在声明变量时,可以使用赋值运算符(=)将值赋给变量:

let name = 'jiayi';

console.log(name); // "jiayi"

在使用变量之前一定要初始化它们,否则会出现错误:

console.log(name);  // ReferenceError: name is not defined

let name = 'jiayi';

ECMAScript 2015(或ES6)引入了两种声明变量的新方法:letconst。使用新关键字的原因是var的函数作用域令人困惑。这是JavaScript bug的主要来源之一。

作用域

从范围上讲,我们正在讨论运行时代码不同区域中变量的可见性。换句话说,代码的哪些区域可以访问和修改变量。

在JavaScript中,作用域有两种:

  • 全局作用域
  • 局部作用域

现代JavaScript (ES6+)所改变的是我们在局部作用域中使用变量的方式。

全局作用域

在函数外部声明的变量成为全局变量。这意味着可以在代码中的任何地方访问和修改它。

我们可以在全局作用域中声明常量:

const COLS = 10;
const ROWS = 20;</span>

我们可以从代码的所有区域访问它们

局部作用域

在局部作用域中声明的变量不能从局部作用域中外部访问。相同的变量名可以在不同的作用域中使用,因为它们被绑定到各自的作用域中。

局部作用域因所使用的变量声明而不同。

函数作用域

var关键字声明的变量成为函数的局部变量。可以从函数内部访问它们。

function printColor() {
 if(true) {
    console.log(color); // undefined
    var color = "pink";
    console.log(color); // "pink"
  }
  console.log(color); // "pink"
}
  printColor();
  console.log(color); // ReferenceError: color is not defined

我们可以看到,即使我们在声明之前访问color,也不会出错。使用var关键字声明的变量会被提升到函数顶部,并在代码运行之前用undefined进行初始化。通过提升,即使在声明之前,也可以在其封闭范围内访问它们。

你能看出这是如何让人困惑的吗?

块级作用域

在ES6中引入了块作用域的概念,以及声明变量constlet的新方法。这意味着变量可以在两个大括号{}之间访问。例如在iffor里面。

function printColor() {
 if(true) {
    console.log(color); // ReferenceError: Cannot access 'color' before initialization
    let color = "pink";
    console.log(color); // "pink"
  }
  console.log(color); //  ReferenceError: color is not defined
}
  printColor();
  console.log(color); // ReferenceError: color is not defined

letconst变量只有在定义求值后才初始化。它们不会像函数范围中那样被提升。在初始化之前访问它们会导致ReferenceError

我希望你们能看到这是如何更容易推理的。

Const vs Let

因此,既然我们知道应该使用 letconst,那么什么时候应该使用它呢?

两者的不同之处在于 const 的值不能通过重新赋值改变,也不能被重新声明。因此,如果我们不需要重新赋值,我们应该使用const。这也使得代码更加清晰,因为我们用一个变量来表示一个不可变的值。

甚至可以始终将变量声明为const,直到看到需要重新分配变量然后更改为let为止。 何时需要让我们进入循环的一个示例:

for(let i = 0; i < 10; i++) {
  console.log(i); // 1, 2, 3, 4 …
}

Var vs Let

我们为什么要使用let,而不是var

我们写一个循环,最终符合预期输出:

for(var i = 0; i < 10; i++) {
  console.log(i); // 1, 2, 3, 4 …
}

这是很常见的操作,看不出什么毛病,那么我们修改一下写法:

for(var i = 0; i < 10; i++) {
  setTimeout(function(){
    console.log(i); // 1, 2, 3, 4 …
  }, 1000)
}

我们本以为得到期望输出,但是结果却是 10 个 10

为什么会这样呢。

  1. for 循环会先执行完再执行 setTimeout 回调函数(同步优先于异步优先于回调)
  2. for 循环和 setTimeout 回调函数不在一个作用域。 setTimeout 回调函数属于函数级的作用域,不属于 for 循环体,属于全局。
  3. 等到 for 循环结束,i 已经等于 10 了,这个时候再执行 setTimeout 的五个回调函数,里面的 i 去向上找作用域,只能找到全局下的 i,即 10。所以输出都是 10

那么我们需要需要let来救赎:

for(let i = 0; i < 10; i++) {
  setTimeout(function(){
    console.log(i); // 1, 2, 3, 4 …
  }, 1000)
}

除此之外,也可以通过闭包来解决问题,这是一道常见的经典面试题

总结

  • 声明在全局作用域中的变量在代码中的任何地方都可以访问
  • 在局部作用域中声明的变量不能从局部作用域中外部访问
  • constlet 使用存在于两个大括号 {} 之间的块作用域
  • 通常将 const 用于其值永远不会改变的变量
  • 对于其他的申明使用 let
  • 不要使用 var 以避免混淆

jiayisheji avatar Dec 15 '20 14:12 jiayisheji