let和const命令
1.let命令
let 只在let
命令所在的代码块内有效。
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
for (let i = 0; i < arr.length; i++) {} console.log(i); //ReferenceError: i is not defined var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
不存在变量提升
let
不像var
那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则报错。
console.log(foo); // 输出undefined console.log(bar); // 报错ReferenceError var foo = 2; let bar = 2;
暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123; if (true) { tmp = ‘abc‘; // ReferenceError let tmp; }
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。
不允许重复声明
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; // 不报错 } }
2.块级作用域
ES6的块级作用域
let
实际上为JavaScript新增了块级作用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
内层作用域可以定义外层作用域的同名变量。
{{{{ let insane = ‘Hello World‘; {let insane = ‘Hello World‘} }}}};
块级作用域与函数声明
ES6规定,块级作用域之中,函数声明语句的行为类似于let
,在块级作用域之外不可引用。
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 函数声明语句 { let a = ‘secret‘; function f() { return a; } } // 函数表达式 { let a = ‘secret‘; let f = function () { return a; }; }
另外,还有一个需要注意的地方。ES6的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
// 不报错 ‘use strict‘; if (true) { function f() {} } // 报错 ‘use strict‘; if (true) function f() {}
3.const命令
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
const
声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。对于const
来说,只声明不赋值,就会报错。
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
const
声明的常量,也与let
一样不可重复声明。
对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const
命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。
const foo = {}; foo.prop = 123; foo.prop // 123 foo = {}; // TypeError: "foo" is read-only
如果真的想将对象冻结,应该使用Object.freeze
方法。
const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123;
ES5只有两种声明变量的方法:var
命令和function
命令。ES6除了添加let
和const
命令,后面章节还会提到,另外两种声明变量的方法:import
命令和class
命令。所以,ES6一共有6种声明变量的方法。
4.全局对象的属性
全局对象是最顶层的对象,在浏览器环境指的是window
对象,在Node.js指的是global
对象。
未声明的全局变量,自动成为全局对象window
的属性,这被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了两个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道,其次程序员很容易不知不觉地就创建了全局变量(比如打字出错)。另一方面,从语义上讲,语言的顶层对象是一个有实体含义的对象,也是不合适的。
ES6为了改变这一点,一方面规定,为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是全局对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于全局对象的属性。也就是说,从ES6开始,全局变量将逐步与全局对象的属性脱钩。
var a = 1; // 如果在Node的REPL环境,可以写成global.a // 或者采用通用方法,写成this.a window.a // 1 let b = 1; window.b // undefined