二、作用域的特点
我们上一篇讲解了作用域,现在看一下作用域的特点
看一下这段代码吧

1 声明形式
变量声明:
var x; //局部变量声明
var x=1; //局部变量声明并赋值
x=1; //定义全局变量并赋值
函数声明:
function fn(){}//函数声明并定义
var fn=function(){}//定义局部变量fn和一个匿名函数,并赋值给fn
2 变量提升
引出一个问题
下面这段代码会输出什么内容?

多数人都说输出的是日期。但真实的结果是undefined。为什么是这样呢?这里就引出了一个概念--hoisting,中文的意思就是变量提升。
因为变量声明是在任意代码执行前处理的,在代码区中任意地方声明变量和在最开始(最上面)的地方声明是一样的。也就是说,看起来一个变量可以在声明之前被使用!这种行为就是所谓的“hoisting”,也就是变量提升,看起来就像变量的声明被自动移动到了函数或全局代码的最顶上。
注意:仅仅是申明提升了,定义并不会被提升。
如此,上面这段代码其实就是下面的形式:

推荐的做法是在声明变量的时候,将所用的变量都写在作用域(全局作用域或函数作用域)的最顶上,这样代码看起来就会更清晰,更容易看出来那个变量是来自函数作用域的,哪个又是来自作用域链
3 重复声明


4 函数和变量同时提升的问题
如果是函数和变量类型同时申明定义了,会发生什么事情呢?看下面的代码

而如果是这样的形式呢

为什么会这样呢?
- 原来函数提升分为两种情况: 一种:函数申明。就是function foo(){}这种形式。另一种:函数表达式。就是var foo=function(){}这种形式
- 第二种形式其实就是var变量的声明定义,因此上面第二个图输出结果为undefined应该就能理解了。
- 而第一种函数申明的形式,在提升的时候,会被整个提升上去,包括函数定义的部分!因此跟下面的这种方式是等价的!

1、函数声明被提升到最顶上;
2、申明只进行一次,因此后面var foo='i am text'的申明会被忽略。
3、并且函数申明的优先级优于变量申明,
所以以下形式的输出,同样是函数内容:

5 ES6的块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,会带来下面问题:

ES6引入了块级作用域,明确允许在块级作用域中声明函数,let和const命令都涉及块级作用域。
块作用域的用处:变量的声明应该距离使用的地方越近越好。并最大限度的本地化。避免污染。
块级作用域允许声明函数只在使用大括号的情况下成立,如果未使用大括号,会报错。
if (true) {
function func1() {} // 不报错
}
if (true)
function func2() {} // 报错
1.let
let定义的变量具有以下的特点:
- let隐形的创建块作用域({...}),可以将变量绑定到所在的任意作用域中
- let声明的变量不能进行变量提升,因此只能先定义,后使用
(如下代码)
{
console.log(bar); // ReferenceError
let bar = 2; //此处不会被提升。
}
let一个典型的应用就是在for循环里
我们看下面两个例子:
// 每秒输出一个5
for( var i = 0; i < 5 ; i++ ) {
setTimeout(() => {
console.log( i );
}, i *1000)
}
// 依次输出0,1,2,3,4,时间间隔位1秒
for( let i = 0; i < 5 ; i++ ) {
setTimeout(() => {
console.log( i );
}, i *1000)
}
其原因就是let形成了5个块作用域,使每次输出的变量都从本次循环的块作用域中获取。
2.const
固定常量,不可被修改,否则报错。
var foo = true;
if (foo) {
var a=2;
const b = 3; // 包含在 if 中的块作用域常量
a=3;//正常!
b=4;//错误!
}
console.log( a ); // 3
console.log( b ); // ReferenceError!
6 总结
要彻底理解JS的作用域和Hoisting,只要记住以下三点即可:
- 所有申明都会被提升到作用域的最顶上
- 同一个变量申明只进行一次,并且因此其他申明都会被忽略
- 函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升