let
let用于定义一个变量,这和ES5中的var是一样的作用。但是var定义的变量会存在变量污染的问题。
var a = 1;var a = 2;console.log(a); // 2
虽然可以通过立即执行函数来解决全局变量污染的问题,但是在立即执行函数内的问题无法得倒解决。
(function(){var a = 1;var a = 2;console.log(a); // 2})()
1、同一个变量不容许重复声明
这个特点也就直观的解决了var变量污染的问题
let a = 1;let a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declaredvar b = 1;let b = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared
// 在函数内部声明也是一样的function test() {let a = 1;let a = 2; // Uncaught SyntaxError: Identifier 'a' has already been declared}
// let 声明不能和形参同名function test(a) {let a = 2;console.log(a);}test(1)
// 变量也不能和函数同名{let a = 1;function a() {}console.log(a); // Identifier 'a' has already been declared}
2、块级作用域
在ES5中var是不受块作用域{}限制的
if(true){var a = 1;}console.log(a); // 1
ES6的let声明的变量在块内{}是独立的
if(true){let a = 1;}console.log(a); // a is not defined
function test(a) {{let a = 2;console.log(a); // 2}console.log(a); // 1}test(1);
3、暂时性死区
let不会进行预编译的变量提升,在let之前访问变量会存在暂时性死区,使用var定义的变量会进行变量提升
var a = a;console.log(a); // undefindlet b = b;console.log(b); // Cannot access 'b' before initialization// 预编译阶段:var a;a = a;// let 不会进行变量提升,所以报错:初始化前无法访问“b”
// 全局作用域内,无法在 let 之前访问变量console.log(a); // Cannot access 'a' before initializationlet a = 10;
// 函数的作用域内也无法在 let 之前访问变量function test() {console.log(a); // Cannot access 'a' before initializationlet a = 10;}test();
console.log(typeof a); // Cannot access 'a' before initializationlet a = 1;
4、let只能在当前的块级作用域内生效
这个特点可以和特点2合并成一个
{// let 声明的变量只作用于当前 {} 内let a = 2;}console.log(a); // a is not defined
function test(){let a = 2;}console.log(a); // a is not defined
if(true){var a = 2;let b = 3;}console.log(a); // 2console.log(b); // b is not defined
// for 循环中 () 然后属于 {} 块作用域的范畴for (let i = 0; i < 3; i++) {}console.log(i); // i is not defined
// 针对 for 循环还有一个需要注意的地方for (let i = 0; i < 10; i++) {let i = "a";console.log(i); // 10 个 a}for (let i = 0; i < 10; i++) {var i = "a";console.log(i); // 报错}// 解析// 我们可以把()和 {} 理解为两个块作用域// 第一个循环相当于一个父子块作用域{let i;{let i;}}// 第二个循环的时候,因为 var 会变量提升,所以会出现这样var i;{let i;{}}
🛳 下面是一个经典的**for**循环执行方法的题目:
var arr = [];for (var i = 0; i < 5; i++) {arr.push(function () {console.log(i);});}for (var i = 0; i < 5; i++) {arr[i]();}// 解析// 答案: 0 1 2 3 4// 这道题猛然一看就认为是 5 个 5// 因为 var 会变量提升,for 循环可以拆分出来var i = 0;for (;i < 5;) {// ../i++}// 实际上第二次循环的时候,又 var i 相当于把全局的 i 覆盖了var i = 5;for (var i = 0; i < 5; i++) {// i = 0// i = 1}// 所以覆盖的时候去执行方法,方法去寻找全局的 i,此时就打印出来 0 1 2 3 4
// 正常的题目是这样滴:var arr = [];for (var i = 0; i < 5; i++) {arr.push(function () {console.log(i);});}for (var j = 0; j < 5; j++) {arr[j](); // 这个时候打印的肯定是 5 个 5}// ==========// 当我们把 var 更改为 let 的时候就可以正常打印出 0 1 2 3 4 了var arr = [];for (let i = 0; i < 5; i++) {arr.push(function () {console.log(i);});}for (var j = 0; j < 5; j++) {arr[j]();}// ==========// 解析// 因为 let 有块作用域的功能,所以第一次循环的时候相当于生成 5 个独立的块{let i = 0;function () {console.log(i);}}// 第二次循环的时候,函数会往上找块作用于内的 i ,所以也就能正常的打印了
const
const和let基本上类似,只不过const主要用来定义常量(不可更改的数据)。const的特点:
1、定义常量必须赋值
const a;console.log(a) // Missing initializer in const declaration
2、定义常量后不可更改
const a = 10;a = 11; // TypeError: Assignment to constant variable.
3、不容许重复声明
const a = 10;const a = 12; // Identifier 'a' has already been declaredvar a = 10;const a = 12; // Identifier 'a' has already been declared
4、暂时性死区
和let定义变量是一样的
console.log(a); // Cannot access 'a' before initializationconst a = 10;
5、块作用域
{const a = 10;}console.log(a); // a is not defined
6、定义引用对象无效
const对于「原始数据类型」后更改值是不被容许的,但是定义「引用数据类型」后更改它的属性仍然是可以的,也就是说**const**只能保证引用值的指针不被改变,并不能保证引用值的属性不被改变
const person1 = {};person1.name = "张三";console.log(person1); // {name: "张三"}const person2 = {};person2 = {}; // Assignment to constant variable.
// 我们可以利用 Object.freeze() 方法来冻结对象,达到不能操作属性的目的const obj = {};Object.freeze(obj);obj.name = "张三"console.log(obj); // {}
顶层对象
在使用var定义数据的时候,相当于在window对象上新增了一个属性,这样某些情况下就会造成混乱。
var a = 1;console.log(window.a); // 1
而let和const为了解决这个问题,定义的数据不会存在顶层对象window上。
let a = 1;const b = 2;console.log(window.a); // undefinedconsole.log(window.b); // undefined
