前言
细讲一下es6新增let(块级声明变量)和const(声明常量)两个命令
1. let使用方式和优点
使用方式和var用法一样,但是let声明的变量只会在当前代码块有效
使用方式
使用let最常见的可能就是for循环了
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);// ReferenceError: i is not defined
上面说过let是声明块级变量,我们打印是在for循环外所以会报错
for循环中var和let使用有什么区别呢?
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[5](); // 10
a[6](); // 10
我们发现a数组中所有方法打印的都是10,因为变量i是用var声明的所以变量i一直在改变一直改变到最后一个也就是10
下面这段代码可能会更透彻
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
i = 22
a[5](); // 22
a[6](); // 22
因为变量i是全局声明的当它改变的时候所有用到的地方都会改变。
如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
//let i作用域
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
这段代码变量i是使用let声明的,所以每轮循环变量i就是个新的变量。
ps: JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算
在for循环中使用let还有个特别之处,就是设置循环变量和循环体是父子块级关系
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
如果在循环体中再次声明变量i会把设置的循环变量替换掉
优点
与var相比let很多优点
1.不存在变量提升
// var
console.log(a); // undefined
var a= 2;
// let
console.log(b); // Uncaught ReferenceError: b is not defined
let b= 2;
var这种写法对于严谨的程序员来说简直惨不忍睹,但是架不住一些不注意规范的人去这样写,所以为了纠正这样的写法,let命令声明的变量只能在声明后使用
2.暂时性死区
只要在块级作用域使用let命令,他所声明的变量就会绑定到当前块级作用域。而且会把当前变量封闭在当前作用域
var a = 123;
if (true) {
a = 321; // Uncaught ReferenceError: Cannot access 'a' before initialization
let a;
}
变量a首先被var声明为全局变量,后面在if作用域中再次被let声明绑定到当前块级,所以变量a被封闭在if作用域中,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
var a = 312;
if (true) {
// TDZ开始
a = 'abc'; // Uncaught ReferenceError: Cannot access 'a' before initialization
console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
let a; // TDZ结束
console.log(a); // undefined
a = 123;
console.log(a); // 123
}
在声明前都属于变量死区
暂时性死区也会倒是typeof不在是个安全的操作
typeof x; // Uncaught ReferenceError: x is not defined
let x;
在声明前都属于变量死区,所以typeof会报错
但是如果并没有使用let命令,typeof是不会报错的
typeof undeclared // "undefined"
所以只要不使用let,typeof是百分百安全的,所以使用let变量必须先声明后使用
3.不允许重复声明
let不允许在相同作用域内,重复声明变量
{
let a = "";
let a = "";
}
同样在方法函数中参数也不能重新声明参数
function fun(a) {
let a;
}
fun() // 报错
function fun(a) {
{
let a;
}
}
fun() // 不报错
2.const命令
可声明基本数据类型和引用数据类型,如果声明基本数据类型那么当前变量就是个只读的常量,如果声明的是引用数据类型那么当前变量不可以赋值为其他变量,但是其属性是可变的
1.使用方式
const CONSTANT;// Uncaught SyntaxError: Missing initializer in const declaration
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
{
const CONSTANT = 123;
}
console.log(CONSTANT)// Uncaught ReferenceError: CONSTANT is not defined
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
{
console.log(CONSTANT)// Uncaught ReferenceError: CONSTANT is not defined
const CONSTANT = 123;
const CONSTANT = 321;//Uncaught SyntaxError: Identifier 'CONSTANT' has already been declared
}
const也同样存在死区,也同样不允许重复声明
1.基本数据类型
const CONSTANT = 3.1415;
console.log(CONSTANT)// 3.1415
CONSTANT = 3;
// Uncaught TypeError: Assignment to constant variable.
声明后如果修改值就会报错
2.引用数据类型
{
const object = {
a: 1,
};
object.a = 2;
console.log(object);
object = {};//Uncaught TypeError: Assignment to constant variable.
}
声明的如果是引用数据类型属性是可变的,但不能重新赋其他值(指向的地址是不可变的)
如果想把所有的值包括属性都不可变可以使用Object.freeze方法
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
但是如果数据的值是引用数据类型的话那就要使用嵌套去设置属性值不可变
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};