# 五.作用域链

前言

词法作用域是在定义时确定的,而动态作用域是在运行时确定的

# 1.作用域

  • 在 JS 中,作用域是用来规定变量访问范围的规则
function one() {
  var a = 1
}
console.log(a)
1
2
3
4

# 2.作用域链

  • 作用域链是由当前执行环境与上一层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

# 2.1 作用域链

function one() {
  var a = 1
  function two() {
    var b = 2
    function three() {
      var c = 3
      console.log(a, b, c)
    }
    three()
  }
  two()
}
one()
1
2
3
4
5
6
7
8
9
10
11
12
13
// 1.创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}` }
var globalExecuteContext = {
  VO: globalExecuteContextVO,
  scopeChain: [globalExecuteContextVO],
}
var executeContextStack = [globalExecuteContext]
//2.执行one,创建one执行上下文
var oneExecuteContextVO = {
  a: 1,
  two: `()=>{var b = 2 ;}`,
}
var oneExecuteContext = {
  VO: oneExecuteContextVO,
  scopeChain: [oneExecuteContextVO, globalExecuteContext.VO],
}
//2.执行two,创建two执行上下文
var twoExecuteContextVO = {
  b: 2,
  three: `()=>{var c = 3 ;}`,
}
var twoExecuteContext = {
  VO: twoExecuteContextVO,
  scopeChain: [
    twoExecuteContextVO,
    oneExecuteContext.VO,
    globalExecuteContext.VO,
  ],
}
//3.执行three,创建three执行上下文
var threeExecuteContextVO = {
  c: 3,
}
var threeExecuteContext = {
  VO: threeExecuteContextVO,
  scopeChain: [
    threeExecuteContextVO,
    twoExecuteContext.VO,
    oneExecuteContext.VO,
    globalExecuteContext.VO,
  ],
}
function getValue(varName) {
  for (let i = 0; i < threeExecuteContext.scopeChain.length; i++) {
    if (varName in threeExecuteContext.scopeChain[i]) {
      return threeExecuteContext.scopeChain[i][varName]
    }
  }
}
//console.log(a, b, c);
console.log(getValue("a"), getValue("b"), getValue("c"))
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 2.2 作用域链

  • scopeChain其实是在创建函数的时候确定的
function one() {
  var a = 1
  function two() {
    console.log(a)
  }
  return two
}
var a = 2
var two = one()
two()
1
2
3
4
5
6
7
8
9
10
// 1.创建全局上下文
var globalExecuteContextVO = {
  one: `()=>{var a = 1;}`,
  a: undefined,
  two: undefined,
}
var globalExecuteContext = {
  VO: globalExecuteContextVO,
  scopeChain: [globalExecuteContextVO],
}
//2.开始执行
globalExecuteContextVO.a = 2
//3.开始执行one
var oneExecuteContextVO = { a: undefined, two: `()=>{console.log(a)}` }
var oneExecuteContext = {
  VO: oneExecuteContextVO,
  scopeChain: [oneExecuteContextVO, globalExecuteContextVO],
}
oneExecuteContextVO.a = 1
//4.给two赋值
globalExecuteContextVO.two = oneExecuteContextVO.two
//5.执行two
var twoExecuteContextVO = {}
var twoExecuteContext = {
  VO: twoExecuteContextVO,
  //scopeChain是在创建此函数据的时候就决定了,跟在哪里执行无关
  scopeChain: [
    twoExecuteContextVO,
    oneExecuteContextVO,
    globalExecuteContextVO,
  ],
}
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

如何区分私有变量和全局变量

  • 在全局作用域下声明(预解释的时候)的变量是全局变量
  • 在 私有作用域中声明的变量 和 函数的形参 都是私有的变量
    • 在私有作用域中,我们代码执行的时候遇到了一个变量,首先我们需要确定它是否为私有的变量,如果是私有变量,那么和外面的没有任何的关系;如果不是私有的,则网当前作用域的上级作用域进行查到,如果上级作用域也没有则继续查找,一直找到 window 为止...(作用域链)

当函数执行的时候,作用域链的形成过程

(直接目的:让函数体重的代码执行),首先会形成一个新的私有的作用域,然后按照如下的步骤执行:

  • 如果有形参,先给形参赋值

  • 进行私有作用域中的预解释

  • 私有作用域中的代码从上往下执行

    • 函数形成了一个新的私有的作用域保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的)-->闭包
  • 首选代码从上往下预解释声明 total,然后从上往下执行代码,total 此时没有被赋值所有为 undefined

console.log(total) //-->undefined
var total = 0
1
2
  • 同理私有作用域下面也是,声明了 total 但是没有赋值,结果为 undefiend
var total = 0
function fn(num1, num2) {
  console.log(total) //undefined
  var total = num1 + num2
  console.log(total) //-->300
}
fn(100, 200)
console.log(total) //-->0
1
2
3
4
5
6
7
8
  • 私有作用域下面没有 total,顺着作用域链向上查找,找到全局的 total=0
console.log(total) //-->undefined
var total = 0
function fn(num1, num2) {
  console.log(total) //total 不是私有的找全局下的 total 找这里出现的所有的 total 其实应该是全局的-->0
  total = num1 + num2
  console.log(total) //-->300
}
fn(100, 200)
console.log(total) //-->300
1
2
3
4
5
6
7
8
9

面试题

如何查找上级作用域

  • 看当前函数是在哪个作用域下定义的,那么他的上级作用域就是谁,和函数在哪执行的没有任何关系
var num = 12
function fn() {
  var num = 120
  return function () {
    console.log(num)
  }
}
var f = fn()
f()
~(function () {
  var num = 1200
  f()
})()
1
2
3
4
5
6
7
8
9
10
11
12
13