【ES6新增特性 · (一)】let 和 const 声明变量的新命令,别再傻乎乎的 var 了!

前言:ES5中var声明变量的弊端

ES5中其实给我们提供了两种声明变量的方法:var命令和function命令。使用function命令声明函数这里不参与讨论,我们主要对比var命令。

之前我们在JS入门篇中详细讲过了var命令声明变量有三大特点:

  • 变量可以重复声明;
  • 变量声明会被提升;
  • 没有块级作用域;

这三个特点使得JS代码的编写显得有些“随意”,不够规范,而且这样的语法逻辑也有悖常理,举两个例子:

  1. 内层变量会覆盖外层同名变量:
    下面我们用var声明一个全局变量num赋值为1,之后在show函数作用域的if代码块中重新声明num变量,并赋值2:
var num = 1;

function show() {
    
    
  console.log(num);
  if (false) {
    
    
    var num = 2;
  }
}

show();


——————OUTPUT——————
undefined

最终show函数执行输出结果为undefined
这是因为var声明的变量没有块级作用域,第二次重新声明的num会被提升至函数开头,覆盖了外层的全局同名变量,此时输出的结果必然是undefined。(一个例子中三个特点全都体现)

  1. 各种循环结构中用来计数的循环变量会泄露为全局变量:
    我们在使用循环结构时会声明一个循环的控制变量(比如i,j,k等等),但是循环结束后,它并没有消失,泄露成了全局变量。
for (var i = 0; i < 3; i++) {
    
    
  //...
}
console.log(i);


——————OUTPUT——————
2

更好的做法是我们希望它只在循环控制内有效,循环结束后自动失效,不会影响其他部分的代码执行。这个时候就需要用到我们ES6里新增的let命令了。

一. let 和 const 基础

1.1 let 基础用法

let命令是ES6标准中用来声明变量的新增命令。 它的使命就是来代替var命令的,它的用法类似于var,但是却弥补了var的设计缺陷。

两者最根本的区别在于let命令可以把当前代码块声明为块级作用域(后续章节有详细介绍),使用let命令声明的变量,只在当前代码块内有效。
(代码块以一对花括号{}为单位)

{
    
    
  let a = 1;
  var b = 2;
}

a // ReferenceError: a is not defined.
b // 2

在代码块外访问变量 a 会报未定义的错误,而使用var定义的b变量仍然可以访问到。

现在有了let命令,就可以很好的解决本文开头的第二个问题:循环变量泄露的问题。我们把for循环的计数器ilet声明:

for (let i = 0; i < 3; i++) {
    
    
  //...
}
console.log(i);


——————OUTPUT——————
// ReferenceError: i is not defined

此时变量i就访问不到了。


❀ 拓展一下(一)❀

循环中使用let命令,还有一个要注意的点:每次循环中变量i都会被重新声明。

先来看使用var声明的循环变量i,每次循环中添加一个定时器函数来打印出这个循环变量i

for(var i = 0; i < 3; i++) {
    
    
    setTimeout(function() {
    
    
        console.log(i);
    }, 1000);
}


——————OUTPUT——————
3
3
3

输出了3个3,是因为整个for循环中的循环变量i一直都是同一个变量,最后循环结束变量i最终被赋值2。之后换成let命令再输出一次:

for(let i = 0; i < 3; i++) {
    
    
    setTimeout(function() {
    
    
        console.log(i);
    }, 1000);
}


——————OUTPUT——————
0
1
2

可以看到输出结果和没有加定时器函数一样,这是因为每次循环中变量i都被重新声明了,它之所以还是按正常逻辑输出,是由于JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。


❀ 拓展一下(二)❀

for循环本身还有一个特别之处 —— 设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
    
    
  let i = 'zevin';
  console.log(i);
}


——————OUTPUT——————
zevin
zevin
zevin

输出了 3 次zevin。这表明循环体内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。


1.2 const 基础用法

const命令用于声明一个只读的常量。 声明之初就必须赋值,而且一旦声明就不可更改。

const PI;
// SyntaxError: Missing initializer in const declaration

const PI = 3.1415926;
PI = 3.14;
// TypeError: Assignment to constant variable.

❀ 拓展一下 ❀

const命令的实质

const命令实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

对于基本类型的简单数据(数值、字符串、布尔值)来说,变量所指向的内存地址保存的就是值本身,所以就等同于变量的值不得修改;

const obj = {
    
    };

// 可以添加属性
obj.name = 'zevin';
console.log(obj.name);
// zevin

// 让obj指向另一个对象就会报错
foo = {
    
    }; 
// TypeError: "obj" is read-only

而对于引用数据类型(主要说数组,对象)来说,变量所指向的内存地址保存的是引用地址,只是不能更改引用地址的指向,而对于数组,对象本身来说,我们仍然可以添加或删除元素。


二. ES6变量声明新规范

为了改善ES5中var命令的声明现状,同时也是为了提高JS语言的规范性,ES6中提出了以下四条新的变量声明规范,letconst 命令都适用。

2.1 块级作用域

ES5 只有全局作用域和函数作用域,终于在ES6中新加入了块级作用域。使用letconst 命令声明的变量都只在声明所在的块级作用域内有效。

{
    
    
    let a = 1;
    if(true){
    
    
        const a = 2;
    };
    console.log(a);
}


——————OUTPUT——————
1

代码块以一对大花括号{}为单位,互相之间可以任意嵌套,且互不影响。

{
    
    {
    
    {
    
    {
    
    
  {
    
     const name = 'zevin' }
  console.log(name); 
  // 报错
}}}};

上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。

2.2 不存在变量提升

var命令声明的变量会被提升至文档开头或函数开头,即变量可以在声明之前使用,值为undefined。而在ES6中修复了这一语法行为,letconst 命令所声明的变量一定要在声明后使用,否则报错。

console.log(a);
console.log(b);
console.log(c);

var a = 1;
let b = 2;
const c = 3;


——————OUTPUT——————
undefined
ReferenceError: Cannot access 'b' before initialization
ReferenceError: Cannot access 'c' before initialization

2.3 暂时性死区

ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

if (true) {
    
    
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。


❀ 拓展一下 ❀

“暂时性死区”的出现也意味着typeof不再是一个永不报错的操作。

typeof a; // undefined
typeof b; // ReferenceError
const b = 1;

在变量b使用const声明之前都属于变量b的“死区”,只要用到该变量就会报错。因此,typeof运算符就会抛出一个ReferenceError的错误。

但是如果一个变量根本没有被声明(变量a),使用typeof反而不会报错,而是undefined


2.4 不允许重复声明

ES6中不允许在同一块作用域内,重复声明同一个变量。

if(true){
    
    
    var a = 1;
    let a = 2;
    const a = 3;
}
// SyntaxError: Identifier 'a' has already been declared

所以同理,函数内也不可以使用letconst命令重新定义与形参同名的变量,但是var可以。

function func(num) {
    
    
  let num = 1;
  console.log(num);
}
func() 
// SyntaxError: Identifier 'num' has already been declared
function func(num) {
    
    
  var num = 1;
  console.log(num);
}
func() 
// 1

由于不同块级作用域之间互不影响,所以我们可以在不同块级作用域中定义同名变量。 所以下面的代码不会报错:

function func(num) {
    
    
    var num = 1;
    if(true){
    
    
        let num = 2;
    }else{
    
    
        const num =3;
    }
  }
  func()

三. 拓展:ES6 声明变量的六种方法

ES5 ES6
var
function
let
const
import
class

ES6中其实新增了四种声明变量的方法:本文中介绍了let命令和const命令,后续再来介绍import命令和class命令。再加上ES5中的var命令和function命令,所以ES6 中一共有六种声明变量的方法。

猜你喜欢

转载自blog.csdn.net/JZevin/article/details/108369123