let
let 是 ES6 新增用来声明变量的命令。它与 ES5 的 var 命令类似,但是 let 命令声明的变量,只在 let 所在的代码块内生效
let不能重复声明
eg1:
let a = 1
let a = 2
// Uncaught SyntaxError: Identifier 'a' has already been declared
大家用 let 定义的时候,要注意不能和用 var 一样,是不允许在同一个块级作用域内重复声明同一个变量的。
let 的暂时性死区
只要在块级作用域内使用 let 声明了变量,则在这个作用域内,该变量相当于被绑定了,不会受到其他外部作用域环境的影响。
eg1:
var a = 1
if (true) {
// Uncaught ReferenceError: Cannot access 'a' before initialization
console.log(a)
let a
}
这里暂时性死区,不仅仅是变量定义被绑定了,同时,我们也发现 let 是没有变量提升的。
let的块级作用域
eg1:
{
let a = 1
var b = 2
}
console.log(a) // Uncaught ReferenceError: a is not defined
console.log(b) // 2
这里我们就可以知道 let 声明的变量,只在代码块包裹的范围内生效,但是 var 声明的变量是全局生效的。
我们在学习 ES6 的 let 时,应该都看过很多教程,大部分教程都会用 for 循环来举例
eg2:
ES5 var 定义
for (var i = 0; i < 5; i++) {
console.log(i)
}
console.log(i)
// 0 1 2 3 4 5
ES6 let 定义
// ES6
for (let i = 0; i < 5; i++) {
console.log(i)
}
console.log(i)
// 0 1 2 3 4
// VM420:4 Uncaught ReferenceError: i is not defined
这里我们将这个用 ES6 写的 for 循环通过 Babel 编译后得到这个方法
// ES5
for (var _i = 0; _i < 5; _i++) {
console.log(_i)
}
console.log(i)
对比上面两个 ES6 和 ES5 的实现,我们可以明确看到,在 for 循环内使用 let 定义的 i 变量实际上是另外一个 _i 而不是 i。所以我们在 for 循环执行结束后,打印出来的 i 是 not defined 的。
综上所述,我们可以知道 let 定义的变量确实是在一个块级作用域内部的。
对于在循环中,因为 var 定义的变量在全局的原因,会经常出现一些奇怪的问题
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[3]() // 5
这里,我们可以看到我们不管用调用数组里面第几个数组的方法,最后执行方法得到的都是 5,这里大家可以很明显看到 var 定义的变量,明显不仅仅在循环内部生效,是在全局范围内生效,并且全局只有这一个 i,所以当循环执行结束后,最后只有一个 i,并且 i = 5,所以,最后调用任何一个方法,打印出来的 i 都是同一个 i,都是最后那个等于 5 的 i。
我们之前解决问题的方式,都是采用闭包的方式,相信大家对于这个方式已经非常熟悉了。如下:
var arr = []
for (var i = 0; i < 5; i++) {
// 闭包 返回一个方法,把 i 的值通过闭包传入
arr[i] = (function(i){
return function () {
console.log(i)
}
})(i)
}
现在只需要将 var 改成 let,就可以实现这个效果了
var arr = []
for (let i = 0; i < 5; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[3]() // 3
对于上面代码,变量i是let声明的,当前的 i 只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量,所以最后输出的是3。很多地方都有各种解释,我这里谈下我的理解:
- for()括号内是一个单独的作用域,这里每次 i 都会递增
- for循环函数体内又有一个单独的作用域,相当于在函数体内部,每次 let j = i,有一个 j 中间变量
- 实际上 i 为什么每次可以记住在上次里面递增,是因为JavaScript引擎内部会记住上一轮循环的值
const
const 是用来声明一个常量的,一旦声明则常量的值就不可改变
const 简单介绍
eg1:
const Pi = 3.1415926
Pi = 3
// Assignment to constant variable
const Obj = {}
Obj.name = 'chencc'
Obj = {}
// Assignment to constant variable
上面的代码就是使用 const 定义常量的时候,如果是一个基本类型,则这个基本类型值不会改变了,如果是引用类型,则引用类型的地址值不可以变了,但是引用类型地址指向的堆内存中的内容是可以变化的。
ES5 实现 const
我们考虑下如果我们是使用 ES5 是如何能实现一个 const 呢,这时候就要想到Object.defineProperty,这个之前我也写文章分析过,大家可以往上翻翻找找。
var _const = {}
Object.defineProperty(_const, 'name', {
value: 'chencc',
enumerable: true
})
Object.defineProperty(_const, 'child', {
value: {a: 1, b: 2},
enumerable: true
})
这里是通过属性的方式 configurable writable 不可以改变的方式来实现的一个 const
下面还可以通过 getter 和 setter 来对比的方式来实现
var _const = {}
var temp = 'chencc'
Object.defineProperty(_const, 'name', {
enumerable: true,
get: function () {
return temp
},
set: function (value) {
if (temp !== value) {
console.log('Assignment to constant variable')
} else {
return value
}
}
})
通过 set 的时候进行对比,就可以控制是否改变值,简单实现了 const
总结
以上就是对 let 和 cosnt 的一些学习记录,对于 let 的内部实现,还是有非常多的可以深究的东西,对于内部具体是否实现了变量提升,我们这里根据官方统一都是没有变量提升,但是内部还是有一系列过程,大家感觉可以具体研究下,如果有其他问题,欢迎大家交流指证啦。