ES6复习宝典,你真的了解let和const命令吗

ES6是近些年来JavaScript改动最大的一次,也是现在前端开发的标配,在前端的面试中100%要问的内容。我最近也是在找实习工作,准备全面的复习一遍ES6的内容

ES6的第一篇内容必然是letconst

let命令

在ES6之前,我们声明变量的方式通常是var,但是var是存在很多问题的。ES6之后,我们可以使用let来声明变量,下面我们以几个实际的场景说明一下let的好处以及var的坏处。

产生块级作用域

在使用var的时候,我们经常遇到下面这种问题。

var a = [];
for (var i = 0; i < 10; i++) {
    
    
  a[i] = function () {
    
    
    console.log(i);
  };
}
a[6](); // 10

初学者的逻辑:我在 for 循环的每次循环过程中,都给a数组的相应索引值赋值一个函数,函数内容是输出当前循环的次数。循环完成之后,我调用a[0]就应该输出0,调用a[6]就应该输出6

但是,经过实测,a[6]输出的是10。这是为什么呢?

这是一个非常非常非常经典的问题,无论是面试还是初学前端的人都一定遇到过这种问题。

先说一下原因:上面代码中,变量ivar声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组afunction在运行时,会通过闭包读到这同一个变量i,导致最后输出的是最后一轮的i的值,也就是10。

那么,如何避免这种情况呢?

因为这种情况也不是ES6之后才有的,从JS存在的时候,就存在这种问题,所以可以使用ES6之前的语法来解决,这里我们暂时不讨论,我们只讨论ES6的解决办法,那就是使用let关键字声明变量。

var a = [];
for (let i = 0; i < 10; i++) {
    
    
  a[i] = function () {
    
    
    console.log(i);
  };
}
a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

不存在变量提升

var命令会发生变量提升现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

上面代码中,变量foovar命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量barlet命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。

变量提升是个比较诡异的问题,会产生意想不到的问题,当你在开发中遇到奇怪的问题时,先考虑一下是不是哪里产生使用var声明的变量产生变量提升导致的。

不允许重复声明

在使用var声明变量的时候,是可以重复声明的,不会报任何的错误,新声明的值会替换旧值,这样随性、无约束的声明变量会产生非常多的问题。而使用let声明变量,在相同作用域内,是不允许重复声明一个变量的。

var a = 1;
var a = 2;
console.log(a); // 2

let b = 1;
let b = 2;
console.log(b); //SyntaxError: Identifier 'b' has already been declared

暂时性死区

只要块级作用域内存在let命令,它所声明的变量就绑定(binding)这个区域,不再受外部的影响。

var tmp = 123;

if (true) {
    
    
  tmp = 'abc'; // ReferenceError: Cannot access 'tmp' before initialization
  let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区(temporal dead zone,简称 TDZ)

const命令

基本内容

const命令当对于let命令就简单了很多,它就是用来声明一个只读的变量的,一旦声明,常量的值就不能改变。

const a = 1;

a = 10; // TypeError: Assignment to constant variable.

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

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

上面代码表示,对于const来说,只声明不赋值,就会报错。

const与let相同的特点

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

if (true) {
    
    
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

if (true) {
    
    
  console.log(MAX); // ReferenceError
  const MAX = 5;
}

上面代码在常量MAX声明之前就调用,结果报错。

const声明的常量,也与let一样不可重复声明。

// 以下两行都会报错
const message = "Goodbye!";
const age = 30;

const声明的变量一定无法修改吗?

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {
    
    };

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

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

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

下面是另一个例子。

const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

如果真的想将对象冻结,应该使用Object.freeze方法。

const foo = Object.freeze({
    
    });

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。

除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

var constantize = (obj) => {
    
    
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    
    
    if ( typeof obj[key] === 'object' ) {
    
    
      constantize( obj[key] );
    }
  });
};

总结

ES6新增的letconst命令从理论上来说可以完全替代var命令,如果在面试的时候被问到letconst以及var的区别,可以从上面我列出的几点展开说起,比如:全局作用域和块级作用域变量提升重复声明暂时性死区const是否可以修改。大部分人可能只知道从作用域的角度来叙述它们三者的不同,这是远远不够的,尽可能多的在各个角度分析问题,才能在面试中展示出你的能力。

QQ:505417246
微信:18331092918
微信公众号:Code程序人生
个人博客:http://rayblog.ltd

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_46171043/article/details/118863460