js词法作用域与动态作用域

104 阅读3分钟

1.1 作用域

作用域是指程序源代码中定义变量的区域;

作用域规定了如何查找变量, 也就是确定当前执行代码对变量的访问权限;

javascript是词法作用域(lexical scoping), 也就是静态作用域;

1.2 静态作用域和动态作用域

函数的作用域是在调用的时候确定的

var value = 1;

function foo(){
  console.log(value)
}

function bar(){
  var value = 2
  foo()
}

bar() // 结果???

假设javascript使用的是静态作用域, 分析下执行流程:

  • 执行foo函数, 先从函数内部查找是否有value变量, 如果没有, 就根据书写位置, 查找上面的代码, 也就是value=1,打印的结果也就是1;

假设javascript使用的是动态作用域, 分析下执行流程:

  • 执行foo函数, 从函数内部查找是否有value变量, 如果没有, 就从调用函数的作用域, 也就是bar函数内部查找,结果打印2;

前面我们已经说过, JavaScript使用的是静态作用域, 所以这个结果是1

1.3 动态作用域

什么是动态作用域?

bash就是动态作用域, 把下面的脚本命名为scope.bash, 然后进入相应目录, 执行命令bash ./scope.bash, 看看打印的值是什么:

value = 1

function foo(){
  echo $value
}

function bar(){
  local value = 2
  foo
}
bar

2. 执行上下文

JavaScript并非一行一行的执行, 而是一段一段的分析执行, 当执行一段代码的时候, 会先进行"准备工作", 那这个一段一段是怎么划分的呢?

到底JavaScript遇到什么样的代码会进行准备工作呢?

console.log(add2(1,1)) // 2

function add2(a, b) {
  return a + b
}
console.log(add1(1,1)) // 报错 add1 is not a function
var add1 = function(a, b) {
  return a + b
}

// 用函数语句创建的add2, 函数名称和函数体均被提前, 所以可以执行
// add1 通过var 创建一个变量 变量的值是函数, 只有变量声明提前了,值是undefined, 但是函数体还是在原来的位置, 所以会报错

2.1 可执行代码

JavaScript可执行代码有三种: 全局代码, 函数代码, eval代码;

当执行到一个函数的时候, 就会进行"准备工作", 而这个"准备工作"专业点就叫执行上下文;

2.2 执行上下文栈

JavaScript引擎创建了执行上下文栈来管理执行上下文

假设执行上下文栈是一个数组:

ECStack = []

当JavaScript开始解释执行代码的时候, 最先遇到的是全局代码, 初始化的时候就会把全局代码压入执行栈

ECStack = [
   globalContext
]

当JavaScript遇到下面这段代码:

function fun3(){
  console.log('fun3')
}

function fun2(){
  fun3()
}

function fun1(){
  fun2()
}

fun1()

当执行一个函数的时候, 就会创建一个执行上下文, 并且压入执行上下文栈, 当执行完毕的时候, 就会将函数的执行上下从栈中弹出

// 伪代码

// fun1
ECStack.push(<fun1> functionContext)

// fun1执行了fun2 所以创建fun2的执行上下文
ECStack.push(<fun2> functionContext)

// fun2执行了fun3 所以创建fun3的执行上下文
ECStack.push(<fun3> functionContext)

// fun3执行完毕
ECStack.pop()

// fun2执行完毕
ECStack.pop()

// fun1执行完毕
ECStack.pop()

// JavaScript继续执行接下来的代码, 但是要记住执行上下文栈中最底层永远有一个全局代码globalContext