文法(Grammar)

文法是指编程语言中规则的集合,描述了语言的语法结构和组成部分。它定义了程序的基本构成单元,以及这些单元如何合法地组合在一起。文法通常分为 词法语法 两部分,它是理解编程语言的基础。

1. 词法(Lexical)

词法定义了语言中的基本构件(例如关键字、标识符、运算符、分隔符、常量等),这些构件是编程语言的基本单位,可以视作词汇表。

  • 关键字:如 if, else, for, while 等,这些是语言保留的特殊单词,用于定义控制结构或其他特殊语法功能。

  • 标识符:用于命名变量、函数、类等实体的名称,如 myVariable, sum, getName 等。

  • 运算符:如算数运算符(+, -, *, /)或逻辑运算符(&&, ||, !)。

  • 分隔符:如括号((), [], {})和逗号(,),用于界定代码块或分隔表达式。

2. 语法(Syntactic)

语法则定义了如何将这些词法单位组合成更大的结构(如语句、表达式、函数、类等)。语法规则决定了编程语言的句法结构,表示合法程序的结构。

  • 表达式:由操作符和操作数组成的语法单位,例如 a + bx > 10

  • 语句:完成某种操作的最小单位,通常包括变量声明、控制流等,如 let x = 5;if (x > 10) { ... }

  • 函数:用来执行特定任务的代码块,通常包含定义参数、执行计算、返回值等语法。

  • 类与对象:在面向对象编程语言中,类是对象的蓝图,定义了属性和方法,而对象则是类的实例。

通过词法和语法,编程语言定义了如何从简单的构件(如数字、变量名)构建出更复杂的程序结构(如表达式、函数、类等)。

举个例子:

在 JavaScript 中,以下代码:

let x = 5;

它的词法部分是:

  • let:关键字

  • x:标识符

  • =:运算符

  • 5:常量

语法结构是:let 后接标识符 x,再接赋值运算符 = ,最后接常量 5,这是一条变量声明语句。


语义(Semantics)

语义是指语言元素在执行时所产生的意义和行为。换句话说,语义描述了程序中各个元素在运行时如何操作数据、如何影响程序的执行。语义强调“做什么”而不是“怎么做”。

语义可分为 静态语义动态语义

1. 静态语义

静态语义与语法紧密相关,描述了程序在编译阶段(或在解释器执行前)应遵循的一些规则,这些规则不依赖于程序运行时的状态。

  • 变量作用域:定义了变量的生命周期、可访问范围。比如在 JavaScript 中,变量在函数内部声明时是局部的,函数外部的变量是全局的。

  • 类型检查:编程语言通常会规定某些操作只能应用于特定类型的数据。例如,在 JavaScript 中,不能将字符串与数组相加。

  • 命名规则:如 JavaScript 中,不能使用保留字(如 let, const, return)作为变量名。

2. 动态语义

动态语义关注的是程序执行时,语言元素如何改变计算状态,以及它们如何与计算机资源(如内存、寄存器等)交互。它描述了程序运行过程中如何通过特定的操作改变变量、执行逻辑等。

  • 变量赋值:如 x = 5; 将数值 5 赋值给变量 x,在程序执行时,x 的值就变成了 5

  • 控制流:如 if (x > 10) { ... },在程序运行时会根据 x 的值决定是否进入 if 语句块。

  • 函数调用:当调用 myFunction() 时,程序会跳转到 myFunction 的实现并执行它的代码,执行过程中会有返回值、参数传递等。

举个例子:

let x = 5;
x = x + 1;
  • 静态语义x 是一个有效的标识符,且可以被赋值。

  • 动态语义:程序执行时,x 的值从 5 被更新为 6


运行时(Runtime)

运行时是指程序在执行时,如何进行具体的操作和状态管理的过程。它描述了程序在计算机上实际执行时的行为,如变量存储、内存分配、函数调用、异步执行等。可以认为,运行时关注的是 “如何执行”,而不是 “做什么”

1. 执行上下文(Execution Context)

执行上下文是代码执行时的一种运行时环境。它是程序运行的抽象结构,定义了代码的执行环境、变量的生命周期和作用域链。

  • 全局执行上下文:当脚本第一次运行时,它会创建全局执行上下文,所有在脚本外部的代码都会在这个上下文中执行。

  • 函数执行上下文:每当调用一个函数时,会为该函数创建一个新的执行上下文,包含该函数的局部变量、参数、作用域链等。

  • 执行栈:JavaScript 使用执行栈(Call Stack)来管理执行上下文。每当一个执行上下文被创建时,它会被推入栈中,执行完成后会被弹出栈外。

2. 内存管理

运行时也涉及内存的管理,包括 内存的分配、垃圾回收等操作。

  • 栈内存:通常用于存储基本数据类型(如数字、布尔值)和函数调用的执行上下文。栈内存的分配和释放遵循先进后出的规则。

  • 堆内存:用于动态分配内存,通常存储对象、数组等复杂数据结构。堆内存的管理由垃圾回收机制自动处理。

3. 异步执行与事件循环

JavaScript 是单线程的,但可以处理异步操作。运行时通过事件循环和任务队列来管理异步任务的执行顺序。

  • 事件循环:JavaScript 运行时不断检查任务队列,查看是否有待执行的任务。宏任务和微任务会按照特定顺序执行。

  • 异步任务:例如,setTimeoutPromiseasync/await 等会让程序继续执行其他任务,而不是阻塞主线程,等待这些异步任务的完成。

举个例子:

console.log("Start");
setTimeout(() => {
    console.log("Timeout");
}, 0);
console.log("End");
  • 执行过程

    1. 首先输出 “Start”。

    2. 然后 setTimeout 设置一个异步任务,并继续执行 console.log("End"),输出 “End”。

    3. 最后,事件循环检查到 setTimeout 任务,输出 “Timeout”。


总结

  • 文法 描述了编程语言的结构和组成元素,确保程序符合语言的规则并且可以解析。

  • 语义 描述了程序各个部分在运行时的行为和意义,帮助开发者理解代码的实际效果和业务逻辑。

  • 运行时 涉及程序的实际执行过程,包括执行上下文的管理、内存的分配、异步任务的调度等。运行时是代码从抽象到现实的过渡,它决定了程序的执行顺序和资源管理。

这三者之间的关系可以理解为:文法提供了语言的语法规则,语义则定义了这些规则的实际含义,而运行时则负责实际的执行过程和资源管理。