作用域
作用域是可访问变量的集合或者说范围(例如全局的范围、函数的范围、语句块的范围),在作用域内,变量可访问,在作用域外变量不可访问。
js是词法作用域,变量的访问范围仅由声明时候的区域决定。
js的作用域可以分为3类
-
全局作用域:在全局环境下声明的变量。在任意位置可以访问到。
-
函数作用域:在函数内部声明的变量,函数内部和函数内部声明的函数中都可以访问到。访问变量时候先在函数内部找,找不到则在外层函数中找,直到全局作用域,形成“作用域链”。 函数作用域有“变量提升”和“函数声明提升”的特性。
-
块级作用域:在语句块声明的变量,使用let和const声明的变量才作用于块级作用域,块级作用域没有变量提升。
案例一(初识)
var a = 1;
function f() {
console.log(a);
}
function n() {
var a = 2;
f();
}
n();
分析:前面我们提到了js中的作用域是词法作用域,在声明的时候就已经决定了,所以这里a是会先在f函数中查找,查找不到会去全局查找,所以打印 1 。
案例二(感知)
console.log(a);
a = 10;
console.log(b);
var b = 20;
var c = 30;
let c = 40
console.log(c);
分析:第一个log会报错,因为a没有被声明,第二个log中的b用var声明的,存在变量提升,打印undefined。第三个log也会报错,因为let和const不允许重复声明。
案例三(不惑)
var arr = ["第一次输出", "第二次输出", "第三次输出"];
for (var i = 0; i < arr.length; i++) {
setTimeout(function () {
console.log(arr[i]);
}, i * 2000)
}
分析:考察的是setTimeout是异步执行的,此时的for循环里面使用的是var没有作用域,所以当for循环执行完毕的时候,i的值等于3 ,而arr[3]是undifined。
案例四(洞玄)
for (var i = 0; i < 5; i++) {
console.log(i);
}
console.log(i);
for (let t = 0; t < 5; t++) {
console.log(t);
}
console.log(t);
分析:第一个for循环由于js没有块级作用域,对于有块级作用域的语言来讲,初始化定义的变量,只存在于循环体中,所以第一个log打印的是 0 1 2 3 4 。而此时js循环体已经执行完毕,但是执行的结果仍然存储在执行体外部的执行环境中,所以第二个log打印的是5。第三个log中let存在作用域,所以打印 0 1 2 3 4 。而在for循环体外得不到i的值,所以第四个log是t is not defined。
案例五(知命)
改造下面的代码,使之输出0-9,写出你能想到的所有写法?
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
分析:for循环属于同步执行,for循环执行完毕才会执行setTimeout异步执行队列,当for循环执行完毕。此时的i是10,所以打印出来的i是10个10。
方法一
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
分析:将var变量改成let,特点是:使用let声明的变量会在当前作用域内。
方法二
for (var i = 0; i < 10; i++) {
setTimeout(i => {
console.log(i)
}, 1000, i)
}
分析:setTimeout回调函数的第三个参数可以作为第一个参数传入
方法三
for (var i = 0; i < 10; i++) {
let _i = i
setTimeout(() => {
console.log(_i)
}, 1000)
}
分析:使用_i去存储i的值,let去创建独立的_i作用域
方法四
for (var i = 0; i < 10; i++) {
(i => {
setTimeout(() => {
console.log(i)
}, 1000)
})(i)
}
分析:利用函数自执行的方式,将i作为参数传递进去,构建块级作用域。
方法五
for (var i = 0; i < 10; i++) {
setTimeout(console.log(i), 1000)
}
分析:setTimeout的第一个参数一般只接收函数或者字符串,如果第一个参数是语句的话,js引擎会自动把该语句的外层包裹一层函数。