1、let命令
一、基本用法
首先看下面一段代码:
var a = [] for (var i = 0; i < 10; i++) { a[i] = function () { return i } } a.map(fn => { console.log(fn()) })
以上代码的运行结果可能是什么?
0, 1, 2, ... 9 ?
结果是:连续输出了10次 10
简单的解释就是,当使用map遍历a中的每一个fn时,当fn执行时,在作用域链上查找i时,找到的i是最终已经变成了10的i,而不会是我们所预期的0~9。这是由js这门语言本身所决定的,所以不要觉得奇怪。
那么如何解决?
1、使用闭包与立即执行函数
var a = [] for (var i = 0; i < 10; i++) { a[i] = (function (num) { return function () { return num } })(i) } a.map(fn => { console.log(fn()) })
在以上的代码中,我们将a[i]赋值为一个立即执行的函数,并将i作为参数传递进去,而这个立即执行函数内部又返回了一个函数,从而构成了一个闭包,由于闭包可以“捕获”外部函数的变量,因此,里面的这个函数也就“捕获”到了外部这个立即执行函数的num参数,当for循环结束后,尽管i最终还是10,但是每一次的立即执行函数都会创建一个新的闭包,每个闭包都拥有各自的作用域,都会“捕获”到外部num参数并保存,所以输出结果为:
2、使用let声明
使用闭包与立即执行函数的方法固然可以,但是对于很多初学者来说,并不是件很容易的事,而且,过度使用闭包也不是一个好的习惯。
好在ES6增加了let命令,使用let命令所声明的变量,只在let所在的代码块内有效。
1 var a = [] 2 for (let i = 0; i < 10; i++) { 3 a[i] = function () { 4 return i 5 } 6 } 7 a.map(fn => { 8 console.log(fn()) 9 })
输出结果为:
在上面的代码中,变量i是let声明的,当前的i只在本轮循环中有效,所以每一次循环的i其实是一个新的变量,所以每一次的i并不是相同的
二、不存在变量提升与暂时性死区
1、不存在变量提升
let命令不会像var那样发生变量声明提升的现象。
console.log(i) // ReferenceError let i = 1
而如果使用var 来替换此处的i,那么输出的结果将为: undefined
console.log(i) // undefined var i = 1
2、暂时性死区
只要块级作用域内存在let命令,那么这个let所声明的变量就“绑定”在了这个区域里
es6规定:如果区块中存在let和const命令,那么这个区块对这些命令所声明的变量从一开始就形成了封闭作用域。只要在声明这些变量之前使用这些变量,就会报错。
总之,在一个代码块内部,使用let所声明的变量,在其被声明之前,都是不可用的,称之为暂时性死区
if (true) { // 暂时性死区开始 val = "123" // ReferenceError console.log(val) // ReferenceError let val // 暂时性死区结束 console.log(val) // undefined }
3、使用let,不允许重复声明变量
// 报错 function test () { let val let val }
二、块级作用域
在es5中,只有全局作用域与函数作用域两种,并没有块级作用域的概念。
首先看下面一段代码:
var val = new Date() function test () { console.log(val) if (false) { var val = "hello world" } }
以上代码的输出结果如下:
之所以会是undefined,也正由于之前所提到的变量声明提升所造成的,实际上,上面的这段代码中的test函数中的内容被js引擎解析为下面的结果:
function test () { var val console.log(val) if (false) { val = "hello world" } }
所以,输出的结果会是undefined
但是,如果我们使用let的话,就不会存在这样的问题。因为let实际上为js新增了块级作用域这个概念
function test () { let n = 5 if (true) { let n = 10 } console.log(n) // 5 }
在上面的函数中,有两个代码块,都声明了变量n,但是运行的结果却是5,这说明外层的代码块并不会受内层代码块的影响。
外层作用域无法读取内层作用域的变量,内层作用域可以定义与外层作用域中相同名字的变量。
那么这里就有一个问题值得思考,我们原来所使用的的立即执行函数,使用立即执行函数的目的是什么? 其实就是为了构造一个块级作用域出来,那么既然let可以构造块级作用域,那么let 可不可以在某些地方代替立即执行函数呢? 答案是可以的
// 立即执行函数写法 (function () { var val = "hello wolrd" })() // 块级作用域写法 { let val = "hello wolrd" }
三、const命令
const用来声明常量,所谓的常量就是,一旦声明与初始化后,其值就不允许被改变
这也意味着const一旦声明常量,就必须立即初始化
const PI = 3.14 PI = 1 // TypeError: Assignment to constant variable const val // SyntaxError: Missing initializer in const declaration
const的作用域与let相同:只在声明所在的块级作用域内有效, 并且const所声明的常量也不会被提升,也同样存在暂时性死区,也不允许重复声明
看下面一段代码:
const obj = {} obj.name = ‘obj‘ console.log(obj) // Object {name: "obj"}
有没有跟你想象的结果不一样?不是说const所声明的变量被初始化后就不能再被改变了吗?
这是因为:对于复合类型的变量来说,变量名并不指向数据,而是指向数据在内存中存放的地址,const去声明此类的变量,const命令只能保证变量名所指向的地址是不改变的,但是不能保证改地址里面的内容是否发生了变化。
所以,在上面的代码中,const只保证了obj所指向的地址不发生变化, 不保证obj所指向的地址里面的内容是不变化的。
以上就是对let与const命令的简短介绍,如果有什么不对的地方,还请读者指正。
参考书籍: 《ES6标准入门--阮一峰》