作用域(scoping)

JavaScript是 函数级作用域。块(可以理解成一个“{}”),就像if语句,并不会创建一个新的作用域,即 没有块级作用域 。只有函数才会创建新的作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//例1
function fire(bool) {
if (bool) {
var foo = "bar";
}
console.log(foo);
}

fire(true); //=> bar

//例2
var x = 1;
console.log(x); // 1
if (true) {
var x = 2;
console.log(x); //2
}
console.log(x);// 2

变量提升–Hoisting(提升)

Hosting 只提升了命名,没有提升定义

看一下这题:

1
2
3
4
5
var v='Hello World';
(function(){
alert(v);
var v='I love you';
})() //结果:undefined

为什么会是这样的结果?变量提升,JS中声明的变量,不管在哪个地方声明定义的,其这个变量的声明都会默认被提升到作用域的最前面。像上面说的,JS是函数级作用域,也就是说var v='I love you'中的var v;会被提升到它所处的函数的最前面,上面那段代码等价于:

1
2
3
4
5
6
var v='Hello World';
(function(){
var v; //被提升到这个地方
alert(v);
v='I love you';
})()

当解析器读到 if 语句的时候,它发现此处有一个变量声明和赋值,于是解析器会将其声明提升至当前作用域的顶部(这是默认行为,并且无法更改),这个行为就叫做 Hoisting。

这样就懂了,alert后自然是undefined。所以写js代码的时候,要把变量的声明写在作用域的最前端,防止出现意外。

这样再来看这个例子就懂了:

1
2
3
4
5
6
7
8
function fire(bool) {
if (bool) {
var foo = "bar";
} else {
console.log(foo);
}
}
fire(false); //=> undefined

等价于(只提升了声明,没有提升定义)

1
2
3
4
5
6
7
8
9
function fire(bool) {
var foo; //被提示提升到这个地方
if (bool) {
foo = "bar";
} else {
console.log(foo);
}
}
fire(false); //=> undefined

函数提升

函数提升是把整个函数都提到前面去。

我们写函数的时候有两种方式:一种是函数表达式、另一种是函数声明方式。只有函数声明形式才能被提升

函数声明方式提升:

1
2
3
4
5
6
7
8
9
//函数 myTest 是一个声明
function myTest(){
foo();
function foo(){
alert("我来自 foo");
}
}

myTest(); //(执行成功)

函数表达式方式提升:

1
2
3
4
5
6
7
8
9
10
//这里被提升的仅仅是变量名 foo,至于它的定义依然停留在原处。因此在执行 foo() 之前
//作用域只知道 foo 的命名,不知道它到底是什么
function myTest(){
foo();
var foo =function foo(){
alert("我来自 foo");
}
}

myTest(); //(执行失败)

let实现块级作用域–解决变量提升问题

es6 之前,JavaScript 并没有块级作用域,所谓的块,就是大括号里面的语句所组成的代码块

let定义的变量只有在代码块内有效。

1
2
3
4
5
6
7
8
function fire(bool) {
if (bool) {
let foo = "bar";
}
console.log(foo);
}

fire(true); //=> Uncaught ReferenceError: foo is not defined

其次, let定义的变量不存在变量提升:

1
2
3
4
5
6
7
8
function fire(bool) {
if (bool) {
let foo = "bar";
} else {
console.log(foo);
}
}
fire(false); //=> Uncaught ReferenceError: foo is not defined

所以应当尽可能的避免用 var,用 let 来代替,除非需要用到变量提升。

再来几个例子

变量提升

1
2
3
4
5
6
7
8
9
10
var a = 1;

function foo() {
if (!a) {
var a = 2;
}
alert(a);
};

foo();// 结果:2

解释器分析上面的代码时:

1
2
3
4
5
6
7
8
9
10
var a;
a = 1;

function foo() {
var a; // 变量提升
if (!a) {
a = 2;
}
alert(a); // 此时的 a 并非函数体外的那个全局变量
}

想要结果是1怎么办?–创建新的作用域

alert(a) 在执行的时候,会去寻找变量 a 的位置,它从当前作用域开始向上(或者说向外)一直查找到顶层作用域为止,若是找不到就报 undefined。

因为在 alert(a) 的同级作用域里,我们再次声明了本地变量 a,所以它报 2;所以我们可以把本地变量 a 的声明向下(或者说向内)移动,这样 alert(a) 就找不到它了。

1
2
3
4
5
6
7
8
9
10
var a = 1;

function foo() {
if (!a) {
(function() { // 这是上一篇说到过的 IIFE,它会创建一个新的函数作用域
var a = 2; // 并且该作用域在 foo() 的内部,所以 alert 访问不到
}()); // 不过这个作用域可以访问上层作用域哦,这就叫:“闭包”
};
alert(a);
};