原文作者: 阮一峰
本文为学习笔记;相较原文可能会有部分注释及修改
写在前面
ES6 声明变量的 6 种方法
ES5 只有两种声明变量的方法:var 命令和 function 命令。ES6 除了添加 let 和 const 命令,后面章节还会提到,另外两种声明变量的方法:import 命令和 class命令。所以,ES6 一共有 6 种声明变量的方法。
块级作用域
为什么需要块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。比如内层变量可能会覆盖外层变量;用来计数的循环变量泄露为全局变量
let 实际上为 JavaScript 新增了块级作用域。
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
块级作用域与函数声明
在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于 var 声明的变量;
另外,还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
let
不存在变量提升
let 所声明的变量只在其所在的代码块内有效
let 不存在变量提
var 所声明的变量在脚本运行前就将变量都定义了,在脚本中的赋值语句中再对其进行赋值操作,所以变量可以在声明之前使用,值为undefined
let 纠正了这种语法行为,它所声明的变量一定要在变量声明后使用,否则报错
暂时性死区(temporal dead zone,简称 TDZ)
ES6 中如果在区块中存在 let 和 const ,则区块对这些命令声明的变量从一开始就形成了封闭作用域,凡是在声明之前使用这些变量都会报错
ES6 规定暂时性死区和 let、const 语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
在没有let之前,typeof 运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
不允许重复声明
let 不允许在相同作用域内,重复声明同一个变量。
const
const 声明一个只读的常量。一旦声明,常量的值就不能改变;(这意味着,const 一旦声明变量,就必须立即初始化,不能留到以后赋值)
const 的作用域与let命令相同:只在声明所在的块级作用域内有效。
const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
const 的本质
const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
如果真的想将对象冻结,应该使用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] );
}
});
};