目录

🍎 JavaScript 作用域与作用域链

# 概念

# 作用域

  • 作用域:一个代码段所在的区域。它是静态的(相当于上下文对象),在编写代码时就确定了。

    作用域决定了代码区块中和其他资源的可见性。

  • 作用域分类:

    • 全局作用域:任何不在函数中或者大括号中声明的变量,都是在全局作用域中,全局作用域定义的变量可以在程序的任何位置中访问。

      var greeting = 'hello world'
      function greet() {
        console.log(greeting)
      }
      
      1
      2
      3
      4
    • 函数作用域:也叫局部作用域。变量在函数内部声明,这些变量只能在函数内部访问,不能在函数以外的访问。

      function greet() {
        var greeting = 'hello world'
        console.log(greeting)
      }
      
      greet()
      console.log(greeting) // Uncaught ReferenceError: greeting is not defined
      
      1
      2
      3
      4
      5
      6
      7
    • 块级作用域:ES6 引入了 letconst 关键字。在大括号中使用 letconst 声明的变量存在于块级作用域,在大括号之外不能访问这些变量。(使用 var 声明的不存在块级作用域变量)

      {
        // 块级作用域中的变量
        let greeting = 'Hello World!';
        var lang = 'English';
        console.log(greeting);
      }
      
      console.log(lang); // English
      console.log(greeting); // Uncaught ReferenceError: greeting is not defined
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
  • 作用:隔离变量不同作用域下同名变量不会有冲突

# 词法作用域

又称静态作用域,在变量被创建时就已经确定好了,而非执行阶段确定(也就是说,在写好代码时作用域就确定了),JavaScript 遵循的是词法作用域。

🌰 例子:

var a = 2
function foo() {
  console.log(a)
}

function bar() {
  var a = 3
  foo()
}
bar()
1
2
3
4
5
6
7
8
9
10

上面的运行过程可以看作:29fab3d0-718f-11eb-85f6-6fac77c0c9b3

由于 JavaScript 遵循的是词法作用域,相同层级的 foo 和 bar 没有办法访问到彼此作用域块的变量,所以输出的是 2

# 作用域链

JavaScript 中使用一个变量时,JavaScript 引擎会尝试在当前作用域下去寻找变量,如果没找到会在上层作用域寻找,以此类推直到找到该变量或者到达全局作用域。如果全局作用域找不到该变量则报错。

如果在全局作用域中找不到该变量,(在非严格模式下)隐式声明该变量或者直接报错。

  • 作用域链是多个上下级关系的作用域形成的链,它的方向时从下向上的(从内到外)。

  • 查找变量就是沿着作用链来查找的。

    查找一个变量的查找规则:

    1. 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入 2.
    2. 在上一级作用域的执行上下文中查找相应的属性,如果有直接返回,否则进入 3.
    3. 再次执行 2. 的相同操作,直到全局作用域,如果还找不到就抛出找不到变量的异常。

🌰 例子:

var a = 2
function fn1() {
  var b = 3
  function fn2() {
    var c = 4
    console.log(c) // 4
    console.log(b) // 3
    console.log(a) // 2
    console.log(d) // ReferenceError: Can't find variable: d 
  }
  fn2()
}
fn1()
1
2
3
4
5
6
7
8
9
10
11
12
13

🌰 例子:

var fn = function () {
  console.log(fn)
}
fn()

var obj = {
  fn2: function (){
    console.log(fn2) //ReferenceError: Can't find variable: fn2 
    // console.log(this.fn2)
  }
}
obj.fn2()
1
2
3
4
5
6
7
8
9
10
11
12

# 作用域与执行上下文

  • 创建时机:
    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时。
    • 全局执行上下文环境是在全局作用域确定之后, JavaScript 代码马上执行之前创建。(词法作用域)
    • 函数执行上下文环境是在调用函数时函数体代码执行之前创建
  • 区别
    • 作用域是静态的,只要函数定义好了就一直存在并且不会再变化。
    • 上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会被释放
  • 联系
    • 上下文环境(对象) 是从属于所在的作用域
    • 全局上下文环境从属于全局作用域。
    • 函数上下文环境从属于对应的函数使用域。

注意

两个 n+1:

  • 在判断执行上下文个数中:n 为调用的函数个数
  • 在判断作用域个数中:n 为定义的函数个数
📢 上次更新: 2022/09/02, 10:18:16