重学ES6(一)let和const

335 阅读5分钟

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 的内部实现,还是有非常多的可以深究的东西,对于内部具体是否实现了变量提升,我们这里根据官方统一都是没有变量提升,但是内部还是有一系列过程,大家感觉可以具体研究下,如果有其他问题,欢迎大家交流指证啦。