一步一步重温 JavaScript 基础系列。
本章节将会理解JS的作用域、作用域链以及延长作用域链。
作用域
作用域就是变量和函数的可访问范围。
全局作用域
在代码中任何地方都能访问到的对象拥有全局作用域
- window 对象的内置属性都拥有全局作用域;
- 定义在最外层的函数和变量拥有全局作用域;
- 所有未定义但直接赋值的变量也拥有全局作用域。
局部作用域
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以也称为函数作用域
块级作用域
意思是由花括号封闭的代码块有自己的作用域
没有块级作用域,es6之前,js没有块级作用域,由此会产生问题:在if或者for循环中声明的变量会泄露成全局变量
代码理解如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 例子1:
if(true) {
var color = 'blue';
}
console.log(color)
// 在if语句中声明的变量,会被添加至if语句当前的执行环境中(此时为全局作用域)
// 例子2:
for(var i=0;i < 10; i ++){
// doSomeThing(i)
}
console.log('i:', i)
// 结果会返回10,对于有块级作用域的语言来说,for语句内定义的变量,只会存在于循环体内,
// 而es6之前,js没有块级作用域,由for语句定义的变量i,即使for循环结束后,也依旧存在于for语句所在的执行环境中。
// 例子3:
var temp = 'green';
function foo() {
console.log(temp);
if(false) {
var temp = 'blue';
}
}
foo();
// 结果打印为'undefined'。
// 虽然按照我们常规理解,由于if语句内部代码并不会被执行,在foo函数作用域内,没有temp变量的值,
// 此时向上查找到全局作用域确定temp变量的值为green。
// 但是由于js没有块级作用域,if语句中声明的变量,会被添加至if语句所在的执行环境中,
// 所以在变量提升的前提下,此时temp值为 'undefined'。注意:if语句如果不满足条件,if语句内部代码块不会被执行,但是内部声明的变量(不包括函数)仍然会被添加至if语句所在的执行环境中,此时该变量的值初始化为undefined
模仿块级作用域,ES6以前变量的作用域是函数范围,有时在函数内局部需要一些临时变量,因为没有块级作用域,所以就会将局部代码封装到IIEF(立即执行函数)中。
1
2
3
4
5
6
7
8
9// 立即执行函数
(function(){
var temp = "hello world";
}());
// 块级作用域
{
var temp = "hello world";
}es6块级块级作用域, 用let命令新增了块级作用域,外层作用域无法获取到内层作用域
外层作用域无法获取到内层作用域, 比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 例子1
function fn1() {
var a = 41;
if(1 == 1) {
var a = 3;
console.log(2,a); // 2 3
}
console.log(1,a); // 1 3
}
fn1();
// 打印结果为:2 3 和 1 3
// 例子2
function fn2() {
let a = 41;
if(1 == 1) {
let a = 3;
console.log(2,a); // 2 3
}
console.log(1,a); // 1 41
}
fn2();
// 打印结果为:2 3 和 1 41外层和内层都使用相同变量名,也都互不干扰,比如:
1
2
3
4
5
6
7
8{
{
let name = 'xug';
console.log(name); // xug
}
let name = 'suifeng';
console.log(name); // suifeng
}
明白了块级作用域,模仿块级作用域,咱们再来道老生常谈的旧菜测试一下:如何在for循环中,依次打印出 0,1,2 ?1
2
3
4
5
6
7
8
9
10
11// 方式① 使用自执行匿名函数,模仿块级作用域
for (var i=0; i < 3; i ++) {
(function (a) {
console.log(a)
})(i)
}
// 方式② 使用es6 let,自带块级作用域
for (let i=0; i < 3; i ++) {
console.log(i)
}
作用域链
在确定一个变量的值时,首先会从当前所在的局部作用域内查找,如果找到则停止,否则就会在上级作用域内查找,直到全局作用域。这个逐级向上查找形成的链条,就称之为作用域链。
来道小菜,测试一下:1
2
3
4
5
6
7
8
9
10
11
12
13var scope = "global";
function foo(){
console.log(scope);
var scope = "local";
console.log(scope);
}
foo();
// 打印结果为:'undefined', 'local'
// 在函数foo内部,由于存在变量提升,scope首先会被赋值为'undefined',
// 也就是说在函数作用域内已经找到scope变量的值,查找结束,所以第一个打印为'undefined'。
// 接着,进行变量重新赋值,所以第二个打印为'local'。当然了,这道题的考点主要在变量提升,咱们接着聊。
延长作用域链
虽然执行环境只有全局执行环境和局部执行环境,但是有些语句可以接收一个对象,也就是在当前作用域的最前端临时增加一个变量对象,从而起到了延长作用域链的作用
try-catch 语句中的catch块
catch语句,会创建一个新的变量对象,其中包含的是被抛出来的错误对象的声明with语句
with语句,会将指定的对象添加到作用域链最前端例如:
1
2
3
4
5
6
7
8
9
10function buildUrl () {
var qs = '?debug=true';
with (location) {
var url = href + qs;
}
return url;
}
buildUrl();在这里,with语句接收一个location对象,因此其变量对象就包含了location对象的所有属性和方法,而这个变量对象被添加至作用域链的前端。