文法(Grammar)
文法是指编程语言中规则的集合,描述了语言的语法结构和组成部分。它定义了程序的基本构成单元,以及这些单元如何合法地组合在一起。文法通常分为 词法 和 语法 两部分,它是理解编程语言的基础。
1. 词法(Lexical)
词法定义了语言中的基本构件(例如关键字、标识符、运算符、分隔符、常量等),这些构件是编程语言的基本单位,可以视作词汇表。
-
关键字:如
if
,else
,for
,while
等,这些是语言保留的特殊单词,用于定义控制结构或其他特殊语法功能。 -
标识符:用于命名变量、函数、类等实体的名称,如
myVariable
,sum
,getName
等。 -
运算符:如算数运算符(
+
,-
,*
,/
)或逻辑运算符(&&
,||
,!
)。 -
分隔符:如括号(
()
,[]
,{}
)和逗号(,
),用于界定代码块或分隔表达式。
2. 语法(Syntactic)
语法则定义了如何将这些词法单位组合成更大的结构(如语句、表达式、函数、类等)。语法规则决定了编程语言的句法结构,表示合法程序的结构。
-
表达式:由操作符和操作数组成的语法单位,例如
a + b
、x > 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 运行时不断检查任务队列,查看是否有待执行的任务。宏任务和微任务会按照特定顺序执行。
-
异步任务:例如,
setTimeout
、Promise
、async/await
等会让程序继续执行其他任务,而不是阻塞主线程,等待这些异步任务的完成。
举个例子:
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
-
执行过程:
-
首先输出 “Start”。
-
然后
setTimeout
设置一个异步任务,并继续执行console.log("End")
,输出 “End”。 -
最后,事件循环检查到
setTimeout
任务,输出 “Timeout”。
-
总结
-
文法 描述了编程语言的结构和组成元素,确保程序符合语言的规则并且可以解析。
-
语义 描述了程序各个部分在运行时的行为和意义,帮助开发者理解代码的实际效果和业务逻辑。
-
运行时 涉及程序的实际执行过程,包括执行上下文的管理、内存的分配、异步任务的调度等。运行时是代码从抽象到现实的过渡,它决定了程序的执行顺序和资源管理。
这三者之间的关系可以理解为:文法提供了语言的语法规则,语义则定义了这些规则的实际含义,而运行时则负责实际的执行过程和资源管理。