ES6一:let-vs-const

ECMAScript 6 – let-vs-const

从今天开始学习ECMAScript 6系列语法

var ??

先看一下 MDN 中对于 var 的定义

FROM MDN
————

  1. var声明了一个变量,并且可以同时初始化该变量
  2. 使用var语句声明的变量的作用域是当前执行位置的上下文:一个函数的内部(声明在函数内)或者全局(声明在函数外)。

看个概念—-变量声明提升

FROM MDN
————
由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明。这意味着变量可以在声明之前使用,这个行为叫做“hoisting”。“hoisting”就像是把所有的变量声明移动到函数或者全局代码的开头位置。

啥意思?上个例子理解理解

bla = 2
var bla;
// ...

// 可以理解为:

var bla;
bla = 2;

由于这个原因,总是建议在作用域的最开始(函数或者全局代码的开头)声明变量。这样可以使变量的作用域变得清晰。

再看个概念——块级作用域

块级作用域就不用多说,就是定义的变量只在定义它的块中有效,出了这个块你就不能访问到它了。

在ES6之前,是没有块级作用域这一说的

function getValue(condition) {

    if (condition) {
        var value = "blue";

        // other code

        return value;
    } else {

        // value exists here with a value of undefined
	
        return null;
    }

    // value exists here with a value of undefined
}

如果此时有块级作用域这个概念的话,会不会觉得在else里面无法访问到value变量, 但是因为变量声明提升,就可以在else里面访问到value变量,只是它未初始化,所以其变量值为undefined。 换句话说,就是因为没有块级作用域的概念,此时这段代码实际解析时像这样:

function getValue(condition) {

    var value;

    if (condition) {
        value = "blue";

        // other code

        return value;
    } else {

        return null;
    }
}

JavaScript中var声明的作用域像是Photoshop中的油漆桶工具,从声明处开始向前后两个方向扩散,直到触及函数边界才停止扩散。

感觉看到这里,没看出什么重要的,再接着往下看

let ??

FROM MDN
————

  1. let语句声明一个块级作用域的本地变量,并且可选的赋予初始值
  2. let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,var声明的变量只能是全局或者整个函数块的。

例一:块级作用域

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // 同样的变量!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // 不同的变量
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

例二:for循环

for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域。

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

上面代码输出了3次abc,这表明函数内部的变量i和外部的变量i是分离的。

例三:不存在变量提升

var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined

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

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

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

例四:暂时性死区

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

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

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

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

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(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的“死区”。

例五:不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。

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

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

因此,不能在函数内部重新声明参数。

function func(arg) {
  let arg; // 报错
}

function func(arg) {
  {
    let arg; // 不报错
  }
}

例六:typeof

因为“暂时性死区”的问题,也意味着 typeof 不再是一个百分之百安全的操作。

typeof a; ==> ReferenceError
let a; 

变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError

typeof b ==>undefined

b 是一个不存在的变量名,结果返回 undefined,所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错,现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。

const ??

FROM MDN
————
const声明一个只读的常量。一旦声明,常量的值就不能改变

ES6引入的第三个声明类关键词与let类似:const。

const声明的变量与let声明的变量类似,它们的不同之处在于,const声明的变量只可以在声明时赋值,不可随意修改,否则会导致SyntaxError(语法错误)。

下面的例子演示了常量的特性

// 注意: 常量在声明的时候可以使用大小写,但通常情况下会使用全部大写英文。 

// 定义常量MY_FAV并赋值7
const MY_FAV = 7;

// 在 Firefox 和 Chrome 这会失败但不会报错(在 Safari这个赋值会成功)
MY_FAV = 20;

// 输出 7
console.log("my favorite number is: " + MY_FAV);

// 尝试重新声明会报错 
const MY_FAV = 20;

//  MY_FAV 保留给上面的常量,这个操作会失败
var MY_FAV = 20; 

// MY_FAV 依旧为7
console.log("my favorite number is " + MY_FAV);

// 下面是一个语法错误
const A = 1; A = 2;

// 常量要求一个初始值,用const声明变量后必须要赋值,否则也抛出语法错误
const FOO; // SyntaxError: missing = in const declaration

// 常量可以定义成对象
const MY_OBJECT = {"key": "value"};

// 重写对象和上面一样会失败
MY_OBJECT = {"OTHER_KEY": "value"};

// 对象属性并不在保护的范围内,下面这个声明会成功执行
MY_OBJECT.key = "otherValue";