ES6之let和const

421 阅读6分钟

let基本用法

ES6新增了let属性,于声明变量。用法类似var,但是和var有很多不同。

  1. let声明的变量只在其所在代码块内有效
{
    let a = 10;
    var b = 1;
}
a  //  a is not defined
b  //  1
  1. 不存在变量提升
// var 的情况
console.log(foo)  // undefined
var foo = 2

// let 的情况
congsole.log(bar) // 报错
let bar = 2;
  1. 暂时性死区

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

var tmp = 123
{
    if (true) {
        tmp = 'abc';  // 报错
        let tmp;
    }
}

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

在代码块内,使用let命令声明变量之前,改变量都是不可用的。这在语法上称为“暂时性死区”。

  1. 不允许重复声明

let不允许在相同作用域内重复声明同一个变量, 不管是在前面声明还是在后面声明,都会报错

// 报错
function() {
    let a = 10;
    var a = 1;
}

// 报错
function() {
    let a = 10;
    let a = 1;
}

// 报错
function(arg) {
    let arg; 
}
  1. var 声明的全局变量会自动添加到window对象的属性
var a = 1;
console.log(a)   // 1
console.log(window.a)  // 1 

let b = 1;
console.log(b)  //  1
console.log(window.b)  // undefined

块级作用域

为什么需要块级作用域

ES5只有全局作用域和函数作用域,没有块级作用域,这导致很多不合理的场景

  1. 内部变量覆盖外部变量
var tmp = new Date();
function f() {
    console.log(tmp);
    if (false) {
        console.log(tmp)
        var tmp = 'hellow world'
    }
}
  1. 用来计数的循环变量泄漏为全局变量
for (var i = 0; i < 5; i++) {
    
}
console.log(i)  // 5

块级作用域特点

  1. ES6允许块级作用域任意嵌套
{{{{let insane = 'hello World' }}}};
  1. 外层作用域不能访问内层作用域的变量
{{{{
    {let insane = 'abc';}
    console.log(insane);   // 报错
}}}}
  1. 内层作用域可以定义外层作用域的同名变量
{{{{
    let insane = 'abc';
    {let insane = 'qaz'}
}}}}

块级作用域出现后,匿名函数(IIFE)也就不需要了

// 匿名函数写法
(function() {
    var tmp = ...;
    ...
}())


// 块级作用域写法
{
    let tmp = ...;
}

块级作用域实例

var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = function() {
        consle.log(i);
    }
}
a[6]  // 值是什么

for(let i = 0; i < 3; i++) {
    let i = 'abc';
    console.log(i);
}
// 输出什么 为什么  let改成var 输出什么为什么

块级作用域中声明函数

ES6块级作用域声明函数规则

  1. 允许在块级作用域内声明函数
  2. 函数声明类似var ,即会提升到全局作用域或函数作用域的头部
  3. 函数声明还会提升到所在的块级作用域的头部
function f() {
    console.log('I am outside')
}

(function() {
    if (false) {
        function f() {  // 函数声明式
            console.log('I am inside')
        }  
    };   
    f();
}())

// 在ES6语法环境下会报错 代码执行为
function f() {
    console.log('I am outside')
}

(function() {
    var f = undefined;
    if (false) {
        function f() {  // 函数声明式
            console.log('I am inside')
        }  
    };   
    f();
}())

// 改为函数表达式的语句,就不会报错

注意

ES6块级作用域允许声明函数的规则只在使用大括号的情况下成立,如果没有大括号会报错

do表达式

const命令

  1. const声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.
  1. const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;
// SyntaxError: Missing initializer in const declaration
  1. const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined
  1. const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;
}
  1. 不可重复声明
  2. 声明不会自动添加到window对象的属性

const本质

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

const foo = {};

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

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

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

声明变量的六种方法

var、function、let、const、import、class

顶层对象属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1

a = 2;
window.a // 2

上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。

顶层对象的属性与全局变量挂钩,带来的影响:

  1. 没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的)
  2. 程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。
  3. window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。