JavaScript执行上下文和调用栈

上节课我们已经说过了,JavaScript的代码执行是发生在js引擎中的调用堆栈的,但是具体是如何运行的,我们来详细剖析一下

如何执行上下文

执行上下文:
执行上下文是指在JavaScript中代码被执行时所创建的环境。它包含了变量、函数、对象和其他代码执行所需的信息。每当函数被调用时,都会创建一个新的执行上下文。
举个例子:
例如要把装披萨的盒子比作执行上下文,披萨比作JavaScript的代码,但是盒子里面可能并不只有披萨,还有餐具和清单!这样才能构建一个吃披萨(执行代码)的环境!

只有一个全局执行上下文(EC)
默认上下文,为不在任何函数内的代码创建(顶级)。
JavaScript执行上下文和调用栈_第1张图片

顶级代码示例

JavaScript执行上下文和调用栈_第2张图片

声明的变量和声明的函数在全局中使用,所以在顶级代码中就被执行,但是函数内的代码,只有在被调用的时候菜会执行!

执行函数的内部

内部执行上下文

  1. 可变函数
    a. let、const和var声明
    b. 函数
    c. 参数对象

  2. 作用域链
    JavaScript的作用域链是一种用于查找变量和函数的机制。当在代码中引用一个变量时,JavaScript引擎会按照一定的顺序搜索变量的定义。这个搜索过程就是通过作用域链来完成的。
    在JavaScript中,每个函数都有自己的执行上下文,包括一个变量对象(Variable Object),它存储了函数内部定义的变量和函数。当函数被调用时,会创建一个新的执行上下文,并将其添加到作用域链的前端。
    作用域链是一个由多个执行上下文对象组成的链表,它按照函数定义的嵌套关系进行链接。每个执行上下文对象都有一个指向其父级执行上下文对象的引用,这样就形成了一个作用域链。
    当查找一个变量时,JavaScript引擎首先在当前执行上下文的变量对象中查找,如果找到了变量,则使用该变量。如果没有找到,则沿着作用域链向上一级执行上下文的变量对象中查找,直到找到该变量或者到达作用域链的顶端(全局作用域)。
    如果在整个作用域链上都没有找到该变量,则会抛出一个引用错误。
    这种作用域链的机制允许内部函数访问外部函数的变量,即使外部函数已经执行完毕,也仍然可以通过作用域链找到这些变量。这被称为"闭包",是JavaScript中非常强大和有用的特性之一。

  3. this关键字
    "this"关键字在JavaScript中是一个特殊的关键字,它通常用于引用当前执行代码的上下文对象。
    "this"关键字的值在不同的上下文中有不同的含义。它的值取决于函数的调用方式以及函数所属的对象。
    在全局作用域中,"this"引用的是全局对象(在浏览器中是"window"对象,在Node.js中是"global"对象)。
    在函数中,"this"的值取决于函数是如何被调用的。如果函数是作为对象的方法调用的,那么"this"引用的是该对象。如果函数是作为普通函数调用的,那么"this"引用的是全局对象(在浏览器中是"window"对象,在Node.js中是"global"对象)。
    此外,“this"还可以通过一些特殊的函数调用方式来显式地绑定到指定的对象上,例如使用"call”、"apply"或"bind"方法。
    总之,"this"关键字在JavaScript中用于引用当前执行代码的上下文对象,它的值取决于函数的调用方式以及函数所属的对象。

举例说明

JavaScript执行上下文和调用栈_第3张图片

函数调用栈

“位置”执行上下文被堆叠在一起以跟踪我们在执行中的位置。

JavaScript执行上下文和调用栈_第4张图片

示例

const name = 'Jonas';

const first = () => {
  let a = 1;
  const b = second(7, 9);
  a = a + b;
  return a;
};

function second(x, y) {
  var c = 2;
  return c;
}

const x = first();

首先,让我们逐行解释这段代码的执行过程,以了解函数调用栈的原理是如何应用的:

  1. const name = ‘Jonas’; - 这行代码定义了一个常量name并赋值为字符串’Jonas’。这行代码不涉及函数调用,因此不会影响函数调用栈。在全局代码中就会执行!
  2. const first = () => { … } - 这行代码定义了一个箭头函数first。箭头函数不会立即执行,而是在被调用时才执行。因此,此时不会有函数调用栈的变化。
  3. function second(x, y) { … } - 这行代码定义了一个名为second的函数。函数定义本身不会改变函数调用栈,只有在函数被调用时才会推入调用栈。
  4. const x = first(); - 这行代码调用了函数first并将其返回值赋给常量x。函数调用会改变函数调用栈的状态。
    现在我们来分析函数调用栈的变化:
  5. const x = first(); - 这行代码调用了函数first,将其推入函数调用栈的顶部。
  6. const b = second(7, 9); - 在函数first中,调用了函数second,将其推入函数调用栈的顶部。
  7. return c; - 函数second执行完毕,将其执行结果返回给调用者first。
  8. a = a + b; - 在函数first中,将变量b的值加到变量a上。
  9. return a; - 函数first执行完毕,将其执行结果返回给调用者。
  10. const x = first(); - 函数调用栈中只剩下全局上下文,函数调用栈为空。
    最终,常量x的值为3,因为函数first的执行结果是3。
    这个例子展示了函数调用栈的典型行为:当一个函数被调用时,它的执行上下文被推入调用栈的顶部,函数执行完毕后,它的执行上下文从调用栈中弹出,控制权返回给调用者。这个过程会一直重复,直到所有函数执行完毕,调用栈为空。

你可能感兴趣的:(JavaScript,javascript,开发语言,ecmascript)