let 和 const 是 ES6 新增的用于声明变量和常量的关键字,他们的做用是什么?它们又有什么特性?它们与 var 定义的变量又有何区别?javascript
定义之后可改变的量就是变量,定义后不可改变的量就是常量。前端
在 ES6
中,使用 let
命令定义变量,使用 const
命令定义常量,也就是说 let
定义后变量是可修改的,const
定义后的常量不能被修改。这一特性目前被大多数浏览器原生支持,可是针对少部分不能支持的浏览器,咱们可使用 babel
将它编译成 ES5
语法,下面能够看下二者用 babel
编译后的代码有何区别:java
// ES6
let a = 1;
const b = 2;
复制代码
// babel 编译后
"use strict";
var a = 1;
var b = 2;
复制代码
感受没区别啊??不要急,再加一段代码:webpack
// ES6
let a = 1;
const b = 2;
b = 3;
复制代码
// babel 编译后
"use strict";
function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }
var a = 1;
var b = 2;
b = (_readOnlyError("b"), 3);
复制代码
这样应该很容易看出区别了,当咱们对一个常量进行变值操做,就会抛出一个错误告诉你:这个值是只读的。git
b = (_readOnlyError("b"), 3)
这种操做我也没见过,不过猜想应该相似于b = _readOnlyError("b") && 3
es6
注意:因为const
定义的值是不可变的,这点在用 const
定义引用类型的时候要特别注意!若是是引用类型的 const
值,改变其中的属性是可行的,可是一般不建议这么作。web
const obj = {};
obj.a = 1;
obj.b = 2;
obj; // => {a: 1, b: 2}
obj = {}; // => 报错
复制代码
因为 const
定义的常量,定义后就不能修改的特性,决定了它定义的时候必须就初始化,不然就报错;而 let
则没有这种限制,它定义的变量彻底能够在后面再初始化:面试
let a;
a = 1;
const b; // Uncaught SyntaxError: Missing initializer in const declaration
b = 2;
复制代码
上面这段代码因为直接违反了
const
的语法特性,所以在babel
编译阶段就没法经过express
let
和 var
是两种声明变量的方式,两者主要有如下区别:浏览器
let
所声明的变量会建立本身的块级做用域,建立的做用域是定义它的块级代码及其中包括的子块中,且没法自动往全局变量 window
上绑定属性。var
定义的变量,做用域为定义它的函数,或者全局,而且是能自动往全局对象 window
上绑定属性的。关于可否建立本身的块级做用域这一差异,就会涉及到一道被问烂的面试题:
var result = [];
(function () {
for (var i = 0; i < 5; i++) {
result.push(function () {
console.log(i);
});
}
})();
result.forEach(function (item) {
item()
});
// => 打印出五个 5
复制代码
为何打印出了五个5,而不是预期的 0,1,2,3,4 ?由于 var
不会建立本身的做用域,而 js
自己又是没有块级做用域这个概念的,for
循环中定义的变量就等因而直接定义在匿名函数中的变量,因而当这5个函数被掏出来执行的时候,循环早已完成,而函数读取的上面一层做用域中存储的变量 i
,也早已经被累加成了5。
然而,这个问题只要将 var
关键字换成 let
就迎刃而解:
var result = [];
(function () {
for (let i = 0; i < 5; i++) {
result.push(function () {
console.log(i);
});
}
})();
result.forEach(function (item) {
item()
}); // => 0,1,2,3,4
复制代码
咱们能够看看这段使用 let
的代码,最后被 babel
转译成什么样:
"use strict";
var result = [];
(function () {
var _loop = function _loop(i) {
result.push(function () {
console.log(i);
});
};
for (var i = 0; i < 5; i++) {
_loop(i);
}
})();
result.forEach(function (item) {
item();
});
复制代码
从上面的代码咱们就能够看出,let
建立做用域的方式,其实就是建立了一个函数,在函数内定义一个同名变量并于外部将这个变量传入其中,以此达到建立做用域的目的。
let
定义的变量不会进行变量声明提高操做,也就是说在访问该变量以前必需要先定义。var
定义的变量存在变量声明提高,所以在变量定义前就能访问到变量,值是 undefined
。console.log(a); // undefined
var a = 1;
console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 2;
复制代码
一样咱们来看下上面这段代码被编译后的样子:
"use strict";
console.log(a);
var a = 1;
console.log(b);
var b = 2;
复制代码
看起来好像 Babel
没法编译这种阻止变量声明提高的语法,let
声明的变量没法提高的特性应该是浏览器内部的 JS 执行引擎支持和实现的。
只要块级做用域内存在
let / const
命令,它所声明的变量 / 常量就“绑定”(binding)这个区域,再也不受外部的影响。ES6 明确规定,若是区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就造成了封闭做用域,凡是在声明以前就使用这些变量,就会报错。这种特性也被成为暂时性死区。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError: tmp is not defined
let tmp;
}
复制代码
一样,这个特性也是被浏览器内部的 JS 执行引擎支持和实现的,babel
没法支持这种特性的编译,只能简单的将 let
编译成 var
。可是有意思的是,因为 let
在 if
块中是能够构建本身的独立做用域的,babel
将 tmp
这个变量换了个名字来模拟实现块级做用域的建立:
"use strict";
var tmp = 123;
if (true) {
_tmp = 'abc';
var _tmp;
}
复制代码
let
定义的变量,一旦定义便不容许被从新定义。var
定义的变量,能够被从新定义。var a = 1;
var a = 2;
let b = 3;
let b = 4; // Uncaught SyntaxError: Identifier 'b' has already been declared
复制代码
上面这段代码因为直接违反了
let
定义的变量没法从新定义的语法特性,所以一样在babel
编译阶段就没法经过。
这块内容并不复杂,咱们能够作一个简单的总结:
let
特性:
建立块级做用域。
定义后不能从新定义。
不存在变量提高。
存在暂时性死区。
全局做用域下定义时不会被挂载到顶层对象上(window对象 / global 对象
)
// 浏览器环境
var a = 1;
window.a; // => 1
let b = 2;
window.b; // => undefined
复制代码
const
特性:
let
本篇文章已收录入 前端面试指南专栏