一、let定义的变量不属于顶层对象window
先复习一下var => variable 变量
var a = 5;
console.log(a);
b = 7;
console.log(b);
-------
5
7
写var和不写var都可以正常输出5,7,但是写var和不写var的意义是不同的
写var代表在当前作用域内声明了变量a,不写var相当于对window这个全局对象中的属性b进行赋值。这一点可以通过使用delete函数来测试,这个函数只能删除对象的属性,不能删除变量。
var a = 5;
delete a;
console.log(window.a);
b = 7;
delete b;
console.log(window.b);
--------------
5
undefined // b已经被删除了,无法找到
既然说a是变量不是属性,b是属性,但是下面代码还是可以正常执行
var a = 5;
console.log(window.a);
b = 7;
console.log(window.b);
-----------
5
7
为什么a不是属性,但是window.a不报错还能正常输出呢?这是因为最初在设计JS的时候,将顶层对象的属性和全局变量进行了挂钩,正常的逻辑是不应该这样的,所以这也被多人认为是JS设计败笔之一。
试想,如果是复杂的项目,全局变量都被挂在了window对象下面,那么window对象会越来越大,这就是污染全局变量,而let就很好的解决了污染全局变量这个问题。这也就是ES不断升级的目的。
let a = 5;
console.log(a);
console.log(window.a);
---------------
5
undefined // let定义的a就不能输出了
上面那里有一个需要注意的地方,就是写在src里面的代码(环境构建那节说过src与static目录的区别)是会被webpack打包编译的,打包编译时webpack会屏蔽掉一些问题,比如var a定义的变量,cosole.log(window.a)时就会变成undefined。
二、let不允许重复声明
let a = 1;
let a = 2;
console.log(a);
----------------------------------------------------------------
Uncaught SyntaxError: Identifier 'a' has already been declared
三、不存在变更提升
先来看var定义变量
console.log(a);
var a = 7;
// 上面代码相当于下面的代码
var a;
console.log(a);
a = 7;
---------------------------------------------------------------------------
undefined // undefined不是错误,只有红叉才是错误,此时变量a被提升到console上面了
7
再来看let
console.log(a);
let a = 7;
----------------------------------------------------------------
Uncaught ReferenceError: Cannot access 'a' before initialization
所以,let不存在变更提升~
关于“;”,使用webpack构建时,每行代码后面会被加上“\n”,所以不写";"也是可以的。
四、暂时性死区
防止变量在声明前被使用,使代码更加安全。
// 第一个例子
var a = 5
if(1 == 1) {
a = 6
let a
}
-----------------------------------------------------------------
Uncaught ReferenceError: Cannot access 'a' before initialization
// 第二个例子
function test(a = b, b = 1) {
console.log(a,b)
}
test();
-----------------------------------------------------------------
Uncaught ReferenceError: Cannot access 'b' before initialization
五、块级作用域({~}内就是块)
var定义的变量没有块级作用域,ES5的世界里只有全局作用域和函数作用域,这样会带来很多不合理的场景,比如
// var例子,结果不合理
for (var i = 0; i < 3; i++) {
console.log(i);
}
console.log("for循环外" + i) // i在上面的花括号外面也能取得到值
----------------------------------------------------------
0
1
2
for循环外3
// let例子,结果合理
for (let i = 0; i < 3; i++) {
console.log(i);
}
console.log("for循环外" + i)
------------------------------------------
0
1
2
Uncaught ReferenceError: i is not defined
注意:ES6的块级作用域,是必须有花括号的
if(1 == 1) let a = 7;
--------------------------------------------------------
Uncaught SyntaxError: Lexical declaration cannot appear
in a single-statement context
看一个小面试题,求输出结果
for(var a = 0; a < 3; a++) {
setTimeout(function () {
console.log(a)
})
}
-------------------
(3) 3 // 输出了三次3
因为setTimeout函数是异步操作,当不会马上执行,而且上面的例子中没写时间,就是等到它该执行的时候才会被执行。for循环是同步操作,for执行完之后,setTimeout才会执行。
在JS中有一个叫作事件循环机制eventloop,setTimeout会等主线程空闲,且时间到了的时候才会执行。
闭包
如果需要上面的程序输出0,1,2应该如何改程序呢?
先写一个函数的立即执行就是() (),对就是两个括号,看程序吧还是
// 闭包是以前的知识,它有几个特点,有一个外部函数,还有一个内部函数,内部函数调用外部函数变量
// 这个时候能保证外部函数的变量状态不被释放,也就是每次循环时i的状态,0,1,2不被释放
for(var i = 0; i < 3; i++) {
(function(j) { // j作为形参接收i传过来的值
setTimeout(function () {
console.log(j)
})
})(i) // 通过方法+括号可以直接调用这个方法,因为方法没有名字,所以只能被调用一次
}
-------
0
1
2
上面的代码是一种实现输出0,1,2的方式,通过let也可以实现,见代码
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(j);
});
}
-----------
0
1
2