JavaScript笔记之作用域与声明提前
Kotori Y 27 Posts

JavaScript中的毒瘤——作用域(scope)

作用域

作用域指一个变量或函数和可访问范围,大致分为全局作用域函数作用域块作用域(ES6新增)。

全局作用域

在代码中任何地方都能访问到的对象拥有全局作用域。全局作用域的变量是全局对象的属性,不论在什么函数中都可以直接访问。

1
<script src="myScript.js"></script>
1
2
3
4
// myScript.js

// "global" scope
let counter = 1;

在全局作用域内声明的变量称为全局变量(例如上面的counter),全局变量可从任何作用域访问。

函数作用域

故名意思,在函数定义范围内用var, letconst声明的变量作用的范围即为函数作用域,这些变量只能在函数内部进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = 1;
function f () {
var a = 100
var b = 2
let c = 3
const d = 4
console.log(a) // 100
console.log(b)
console.log(c)
console.log(d)
};
f();
console.log(a); // 1
console.log(b); // ReferenceError
// console.log(c); // ReferenceError
// console.log(d); // ReferenceError

无论用什么声明变量,该变量都无法跨函数访问。

块作用域

在“块(block)”内用letconst声明的变量只能作用于该块内,“块”为 { }内的代码,包括if语句和for语句等里面的{ }。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = 1;
{
var a = 100
var b = 2
let c = 3
const d = 4
console.log(a) // 100
console.log(b)
console.log(b)
console.log(d)
};
console.log(a); // 100
console.log(b); // 2
console.log(b); // ReferenceError
// console.log(d); // ReferenceError
1
2
3
4
5
6
7
8
9
10
11
for (let i = 0; i<=10; i++) {
console.log(i)
};
console.log(i) // ReferenceError

//

for (var i = 0; i<=10; i++) {
console.log(i)
};
console.log(i) // 11

let非常适合用于 for循环内部的块级作用域。JS中的for循环体比较特殊,每次执行都是一个全新的独立的块作用域,用let声明的变量传入到 for循环体的作用域后,不会发生改变,不受外界的影响。看一个常见的面试题目:

1
2
3
4
5
6
7
8
9
10
11
for (var i=0; i<10; i++) {
setTimeout(function() { // 异步
console.log(i) // 此时同步的for循环已经完成
}, 0);
}; // 10个10

for (let i=0; i<10; i++) {
setTimeout(function() { // 异步
console.log(i)
}, 0);
}; // 0 1 2 3 4 5 6 7 8 9

闭包

闭包就是能够读取其他函数内部数据(变量/函数)的函数。只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。

产生闭包的条件

  1. 函数嵌套
  2. 内部函数引用了外部函数的数据(变量/函数)。
  3. 外部函数被调用,内部函数被声明
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
// 没有引入外部函数fn1()的变量,无闭包
function fn1() {
function fn2() {

};
return fn2;
};
fn1();


// fun4采用的是“函数表达式”创建的函数,此时内部函数的声明并没有提前,无闭包
function fn3() {
var a = 3
var fun4 = function () {
console.log(a)
};
};
fn3(); // undefined


// fn6内部函数被提前声明,产生闭包(不用调用内部函数)
function fn5() {
var a = 4
function fn6() {
console.log(a)
};
};
fn5(); // undefined

示例

  • 将一个函数作为另一个函数的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
function fn1() {
var a = 1
function fn2 () {
a++
console.log(a)
};
return fn2
};

var test = fn1(); //执行外部函数fn1,返回的是内部函数fn2
console.log(test) // fn2() {...}
test() // 3 //执行fn2
test() // 4 //再次执行fn2
  • 将函数作为实参传递给另一个函数调用
1
2
3
4
5
6
function showDelay(msg, time) {
setTimeout(function() {
console.log(msg)
}, time);
};
showDelay("Hello World!", 10000)

闭包的作用

  • 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  • 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

声明提前(变量提升)

一道笔试题

1
2
3
4
5
6
7
8
9
console.log(a) //?
a(); //?
var a = 3;
function a(){
console.log(10);
};
console.log(a); //?
a = 6;
a(); //?

使用var关键字声明的变量和使用函数声明的形式创建的函数function foo(){}会在所有代码执行前被声明,但不会赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 99;
function f() {
console.log(a); // undefined // 此作用域内,由变量a通过var声明
var a = 10
console.log(a) // 10
};
f();
console.log(a) // 99

// 上面的代码相当于

var a = 99;
function f() {
var a;
console.log(a); // undefined // 此作用域内,由变量a通过var声明
a = 10
console.log(a) // 10
};
f();
console.log(a) // 99

同样有

1
2
3
4
5
6
7
num(); // 1
console.log(num) // 函数本身
function num() {
console.log(1);
};
num(); //1
console.log(num) //函数本身

对于函数声明提前,变量声明也提前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
console.log(a); // f a() {...}
var a = 100;
function a() {
console.log("Hello World!")
};
console.log(a); // 100

// 调换顺序,结果一致
console.log(a); // f a() {...}
function a() {
console.log("Hello World!")
};
var a = 100;
console.log(a); // 100

// 以上均相当于
function a() {
console.log("Hello World!")
};
var a; // 相同变量名,忽略
console.log(a);
a = 100;
console.log(a);

所以,对于最开始的那道题

1
2
3
4
5
6
7
8
9
console.log(a) // f a() {...}
a(); // 10
var a = 3;
function a(){
console.log(10);
};
console.log(a); // 3
a = 6;
a(); // TypeError, a is not a function

参考链接

  • Post title:JavaScript笔记之作用域与声明提前
  • Post author:Kotori Y
  • Create time:2021-01-04 15:23
  • Post link:https://blog.iamkotori.com/2021/01/04/JavaScript笔记之作用域与声明提前/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
 Comments