深入理解作用域与闭包

1. 作用域是什么?

预计阅读时间: 5 分钟
"作用域

JavaScript 中,作用域(Scope) 是指变量函数对象在代码中的可访问性范围。 作用域决定了在代码的哪些部分可以访问特定的变量或函数,以及在哪些部分它们是不可见的。

2. 作用域的类型

JavaScript 中,作用域可以分为2种类型:

2.1 全局作用域(Global Scope)

"全局作用域

全局作用域是 JavaScript 中顶层作用域,它包含了所有全局变量函数。 所有在全局作用域中声明的变量或函数,可以在代码的任何地方访问。

2.2 局部作用域(Local Scope)

"局部作用域

在ES6之前,局部作用域只有函数作用域一种类型。而在ES6中,新增了块级作用域。

"块级作用域的兼容处理

在ES6之前,if/for等代码块里没有形成作用域,也就是说顶层作用域if/for里面使用var定义的变量会被挂在window上.

ES6之之后,在代码块使用var声明的变量、函数声明在代码块运行之后,仍能在外部被访问到,这是对低版本js的兼容,而不是作用域提升,var定义的变量仍会挂到window上

const/let 定义的变量仍然不能被外层访问到

main.js
1console.log('test', test) // undefined
2foo() // 报错,不是作用域提升
3{
4  var test = 'test'
5  function foo(){
6    console.log('foo')
7  }
8}
9foo() // 正常运行,这是浏览器的特殊兼容处理
10// 这里的test会被挂在到window上去,是作用域提升
11// foo在if代码执行后才能访问,在之前不行,不是作用域提升

2.2.1 函数作用域(Function Scope)

函数作用域是函数内部的作用域,它包含了函数内部声明的变量和函数,实际上也包含了函数的形参。

1function example(param) {
2    var functionVar = "I'm function scoped!";
3    console.log(param); // 可以访问形参
4    console.log(functionVar); // 可以访问函数作用域的变量
5    function innerFunction() {
6        console.log(functionVar); // 可以访问函数作用域的变量
7    }
8    innerFunction(); // 可以访问函数作用域的变量
9}
10example("I'm a parameter");
11console.log(functionVar); // 报错:functionVar is not defined

2.2.2 块级作用域(Block Scope) ES6新增

块级作用域是指在 (花括号)内部声明的变量,它包含了块内部声明的变量和函数,只能在块内部访问。

1if (true) {
2  let blockVar = "I'm block scoped!";
3  console.log(blockVar); // 可以访问块级变量
4}
5console.log(blockVar); // 报错:blockVar is not defined

3. 作用域提升

"作用域提升

变量声明前能被访问,叫作用域提升

简单地说,作用域提升是指在代码执行之前,变量和函数声明会被移动到它们的作用域的顶部-仅声明被移动,还没有初始化值。

var定义的变量会被提升,以及函数声明会被提升,而let/const定义的变量不会被提升。

1console.log(test) // undefined
2var test = 'test'
3
4foo() // foo
5function foo(){
6  console.log('foo')
7}
8
9// 等价于
10var test
11console.log(test) // undefined
12test = 'test'
13
14
15// 作用域提升带来的问题
16var message = "message";
17function foo() {
18  console.log('m', message) // undefined 因为变量被提升,但是没有初始化值
19  var message = 'm' 
20
21}
22foo()

4. 暂时性死区(TDZ)

"暂时性死区

在ES6中,let/const定义的变量在声明之前不能被访问,这种现象称为暂时性死区

暂时性死区就是let/const没有变量提升的特性,即使变量已经存在,但是拦截掉访问权限

1const message = '123'
2 
3 function foo(){
4   console.log(message) //error
5   const message='456'
6 }
7 foo()

5. 作用域链

"作用域链

作用域链是JavaScript中一个非常重要的概念,它决定了变量和函数在代码中的访问顺序。

作用域链
1function foo(count){
2   
3   function bar(num){
4     return count + num  // 访问了外层作用域的变量count
5   }
6   
7   return bar
8}

6. 闭包 (Closure)

"闭包

MDN 闭包 一个函数和对其周围状态(词法环境 lexical environment)的引用捆绑在一起,这样的组合成为闭包

理解的概念:

  • 一个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包;
  • 从广义的角度来说:JavaScript中的函数都是闭包;
  • 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的变量,那么它是一个闭包;
闭包
1function foo(count){
2   
3   function bar(num){
4     return count + num  // 访问了外层作用域的变量count
5   }
6   
7   return bar
8}
9
10var foo3= foo(3)
11foo3(5) //8 foo3就是一个闭包

闭包

7. 执行上下文

详情见执行上下文