# 重学es6系列(1) --块级作用域绑定
JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。 块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
# 1. es5中的var
在很久以前,大家都是使用var 来定义一个js变量。但是使用var会有一个特点, 无论你是在函数作用域中(在一个函数中)或者是在全局作用域中(一个js文件或者html文件中的script标签内)声明这个变量,都会当做在当前作用域的顶部声明的变量,这就是我们常说的提升机制。
console.log(cc)
var cc = 'cc'
// 输出 undefined
console.log(bb)
let bb = 'bb'
// Uncaught ReferenceError: Cannot access 'bb' before initialization
2
3
4
5
6
7
上面一个var是es5的语法, 一个let是es6的语法, 发现输出的结果完全不同,一个输出了undefined一个却报错了。
那是因为在js的预编译阶段,js引擎会将上面的var语法修改成下面这样:
var cc
console.log(cc)
cc = 'cc'
// 输出 undefined
2
3
4
为此
es6引入块级作用域来强化对变量生命周期的控制。let 和 const 都不会进行所谓的变量提升。
# 2. es6中的let和const
# 禁止重复声明
先看一段代码
var aa = 'cc'
var aa = 'aa'
console.log(aa)
// 输出 aa
let bb = 'cc'
let bb = 'bb'
// Identifier 'bb' has already been declared
2
3
4
5
6
7
8
会发现var允许重复声明一个同样名字的变量,那在开发中就要注意了,小心自己定义的变量撞脸了别人的变量。而使用let声明同样名字的变量会报错。
# 块级作用域
块级作用域(亦被称为词法作用域)存在于:
- 函数内部
- 块中(字符{和}之间的区域)
es5之前没有let和const该怎么创建块级作用域呢?
(function(){
// todo
}())
2
3
这样就可以保证不污染外面的变量。
# const 必须初始化
const aa
// Missing initializer in const declaration
2
会报错, 告诉你常量必须初始化。
# const 来声明对象和数组
const obj = { name: 'jack' }
obj.name = 'lili' // ok
obj = { name: 'lili' }
// Assignment to constant variable.
const arr = [1,2,3]
arr[0] = 4 // ok
console.log(arr) // [4,2,3]
arr = [5,2,3]
// Assignment to constant variable.
2
3
4
5
6
7
8
9
10
const
是用来声明常量的, 也就是不可变数据。但是声明为对象和数组时, 却可以改变其中的属性。
# 循环中的块级作用域
# 以往使用var来循环
for (var i = 1; i <= 10; i++) {
console.log(i) // 循环输出1~10
}
console.log(i) // 输出 11
2
3
4
会发现在 for
循环之外也可以访问到i。但是其实我们都是不希望在外面访问到i的,希望的是在for循环结束之后i就要销毁。
# 创建函数
var funs = []
for (var i = 1; i <= 10; i++) {
funs.push(function() {
console.log(i)
})
}
funs.forEach(function (item) {
item() // 输出10个 11
})
2
3
4
5
6
7
8
9
代码输出了10个11, 这不是我们想要的结果。我们希望的是输出每次循环到的值,也就是1~10。这是因为函数会保存对变量的引用,
即在函数执行时才会去取i
最后的值。下面的代码输出的是10个99.
var funs = []
for (var i = 1; i <= 10; i++) {
funs.push(function() {
console.log(i)
})
}
i = 99
funs.forEach(function (item) {
item() // 输出10个 99
})
2
3
4
5
6
7
8
9
10
为了解决这个问题, 我们可以采用立即执行函数来处理。这样就是输出了1~10。将我们想写的代码放入todo里面,这里我们 返回一个函数给数组, 因为立即执行函数会直接执行我们的代码。
var funs = []
for (var i = 1; i <= 10; i++) {
funs.push((function(val) {
return function () {
// todo
return console.log(val)
}
})(i))
}
funs.forEach(function (item) {
item() // 1~10
})
2
3
4
5
6
7
8
9
10
11
12
# 循环中的异步
有一道比较经典的面试题,如何输出1~11,下面的代码只能输出10个11。原因就是循环走完了, 但是异步还没走。等到循环结束了,
这时i = 11
了,开始执行异步。
for (var i = 1; i <= 10; i++) {
setTimeout(function() {
console.log(i) // 10 个 11
}, 0)
}
2
3
4
5
下面有几种方法解决这个问题:
- 采用
setTimeout
的第三个参数, 将外面的参数传入到函数里面。
for (var i = 1; i <= 10; i++) {
setTimeout(function(val) {
console.log(val)
}, 0, i)
}
2
3
4
5
- 还有上面说的立即执行函数来处理
for (var i = 1; i <= 10; i++) {
setTimeout((function(val) {
console.log(val)
}(i)), 0)
}
// 还可以这样
for (var i = 1; i <= 10; i++) {
(function (val) {
setTimeout(function() {
console.log(val)
}, 0)
})(i)
}
2
3
4
5
6
7
8
9
10
11
12
13
- 使用
bind
for (var i = 1; i <= 10; i++) {
setTimeout(function(val) {
console.log(val)
}.bind(null, i), 0)
}
2
3
4
5
- 最好的方法 ,使用es6中的
let
for (let i = 1; i <= 10; i++) {
setTimeout(function() {
console.log(i)
}, 0)
}
2
3
4
5
参考资料: