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