从JS小白到大佬的进化之路:理解JS的执行机制

2 阅读6分钟

JavaScript,作为一门广泛应用于Web前端开发的弱类型语言,它强大、灵活、具有表现力且随处可见。然而,它也带来不少问题,尤其是对于初学者来说,理解JS的底层运行逻辑是一件不太友好的事。

试想一下,在某次编程中,突然发生“变量未定义”的报错,再检查之后,你可能会困惑,因为你发现代码一切正常。这时,问题就有可能出现在底层执行机制上。所以深入了解JS的执行机制是十分有必要的,这是为自己的未来发展奠定基础。

在了解JS执行机制之前,我们先了解下JS这门语言。

JS语言简介

JavaScript是一种动态类型的弱类型语言,没有独立的编译过程。其中变量在运行中确定,可以在运行时改变变量类型。在代码正式执行之前,JS会进行极其短暂的编译过程,在这过程中就会进行解析代码、变量提升等一系列准备工作。然后通过调用栈的方式进行编码。

在简单了解JS这门语言后,我们就可以进一步去了解JS的执行机制。首先来了解下什么是执行上下文和它的生命周期。

执行上下文概述

执行上下文是JavaScript引擎为了即将到来的代码执行做准备,在编译阶段进行。每当一段代码被执行时,都会创建一个新的执行上下文。其中最常见的是全局执行上下文和函数执行上下文。

1. 全局执行上下文

每个JavaScript程序只会有一个全局执行上下文。当JavaScript程序开始执行时,首先创建的就是全局执行上下文,它是整个程序最外层的环境,负责初始化全局变量/函数以及内置对象。 2. 函数执行上下文

每当一个函数被调用时,就会创建一个对应的函数执行上下文。函数执行上下文是暂时性的,随着函数的调用而创建,随着函数的返回而销毁。

二、执行上下文的生命周期

执行上下文的生命周期可以分为三个阶段:创建阶段、执行阶段和销毁阶段。

1. 创建阶段

在创建阶段,执行上下文主要完成初始化所有变量和函数声明、构建作用域链和this绑定的任务。

2. 执行阶段

执行阶段是真正执行代码的阶段,在这个阶段中,变量会被赋予正确的值,表达式和语句会被逐行解释执行。如果在执行过程中遇到了函数调用,则会创建新的执行上下文并将其压入调用栈底。

3. 销毁阶段

当执行上下文中的所有代码都被执行完毕后,该执行上下文会被销毁,释放内存资源。对于函数执行上下文而言,这意味着函数已经返回,局部变量将不再可访问。

三、调用栈的概念

调用栈是一种数据结构,它是JS执行的底层逻辑(是正常运行的关键),它遵循后进先出(LIFO, Last In First Out)的原则。当有函数被调用时,会进行动态编译,将其执行上下文压入栈中;执行完后,该执行上下文就会从调用栈中弹出,进行销毁、释放空间。通过这种方式,JavaScript能够确保函数调用的顺序性和正确性。

四、变量提升与作用域

1. 变量提升

变量提升是指变量和函数声明在代码执行前会被移动到当前作用域的顶部。需要注意的是,只有声明会被提升,而赋值操作依旧留在原地。例如,考虑以下代码片段:

console.log(a); // 输出: undefined
var a = 1;

这里,尽管a的赋值发生在console.log之后,但由于变量提升,a的声明实际上被提前到了作用域的顶部,因此输出结果为undefined而不是报错。需要注意的是,ES6引入的letconst声明则不会发生这样的提升,未初始化前访问它们会导致引用错误。

2. 函数提升 函数提升包括声明和表达式的提升,这意味着函数在声明之前就可以调用。

         function sum(a, b) {
         return a + b; 
         }

在上述代码中,虽然sum函数在声明之前就已经调用,但由于函数声明的提升,所以sum函数可以正常调用。

2. 作用域 在最后,让我们再聊聊作用域的相关知识。

作用域决定了变量和函数的可见性和生命周期。JavaScript中有两种基本的作用域:全局作用域和局部作用域(也称函数作用域)。此外,ES6还引入了块级作用域。

  • 全局作用域:是最外层的作用域,在整个程序之中。在全局作用域中的变量和函数可以在任何地方被调用
  • 函数作用域:是在函数内部创建的作用域,只可以在该函数内部访问。
  • 块级作用域:是在代码块(如if语句、for循环等)内部创建的作用域,只能在代码块内部访问。

特别需要记住的是letconst声明的变量不会被提升。如果在声明前就调用这些变量,就会直接报错。

作用域链是JS中查找变量的重要机制。当在一个作用域中查找变量时,如果在该作用域找不到变量,引擎就会沿着链路往上一级作用域查找,直到找到这个变量。

五、总结

通过本文的介绍,我们了解到JavaScript的执行上下文是代码执行的基础环境,它负责初始化变量、构建作用域链以及绑定this关键字。同时,调用栈则是管理函数调用顺序的关键结构,保证了函数调用的正确性和效率。理解这些概念对于深入掌握JavaScript的工作原理具有重要意义,也有助于开发者编写更高效、更可靠的代码。在日常开发中,合理利用变量提升和作用域规则,可以避免许多常见的编程错误,提高代码的质量和可维护性。