一个思考题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var scope = "global scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
checkscope();
var scope = "global scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
checkscope()();

两段代码执行的结果均输出local scope

JavaScript 采用的是词法作用域,函数的作用域基于函数创建的位置

JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。

两端代码的压栈顺序

答案就是执行上下文栈的变化不一样。

第一段代码:

1
2
3
4
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();

第一代码在执行 checkscope() 时,内部的 f() 也在函数内被执行。因此有连续两次的压栈操作。

让我们模拟第二段代码:

1
2
3
4
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();

在第二段代码中,checkscope() 返回了函数 f,然后在返回的函数 f 上执行了另一次 (),这导致了两次执行上下文的压栈和出栈。

作用域

代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:

  • 最==外层函数==和==在最外层函数外面定义的变量==拥有全局作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var outVariable = "我是最外层变量"; //最外层变量
function outFun() {
//最外层函数
var inVariable = "内层变量";
function innerFun() {
//内层函数
console.log(inVariable);
}
innerFun();
}
console.log(outVariable); //我是最外层变量
outFun(); //内层变量
console.log(inVariable); //inVariable is not defined
innerFun(); //innerFun is not defined
  • 所有末定义直接赋值的变量自动声明为拥有全局作用域
1
2
3
4
5
6
7
function outFun2() {
variable = "未定义直接赋值的变量";
var inVariable2 = "内层变量2";
}
outFun2(); //要先执行这个函数,否则根本不知道里面是啥
console.log(variable); //未定义直接赋值的变量
console.log(inVariable2); //inVariable2 is not defined
  • 所有 window 对象的属性拥有全局作用域

执行上下文栈的流程

  1. 压入全局上下文

  2. 当执行一个到函数时,会创建一个函数执行上下文(Function Execution Context),并将其压入执行上下文栈。这个函数执行上下文包括了函数的参数,以及在函数内部声明的变量和函数。

    在 JavaScript 中,存在全局执行上下文(Global Execution Context)和函数执行上下文(Function Execution Context)。全局执行上下文在浏览器开始读取脚本时创建,只有一个。而函数执行上下文每当函数被调用时都会创建。

    每个执行上下文都有一个关联的变量对象(Variable Object),它包含了在上下文中定义的所有变量和函数。在函数执行上下文中,活动对象(Activation Object)扮演了变量对象的角色,它在函数执行上下文被推入执行上下文栈时创建,包含了函数的参数,以及在函数内部声明的所有变量和函数。

  3. 执行函数执行上下文

    执行上下文的代码会分成两个阶段进行处理:分析和执行,我们也可以叫做:

    • 进入执行上下文

      image-20240229172408618