一、代码是如何运行的

  • 代码是由CPU执行的,而目前的CPU并不能直接执行诸如if…else之类的语句,它只能执行二进制指令。但是二进制指令对人类实在是太不友好了:我们很难快速准确的判断一个二进制指令1000010010101001代表什么?所以科学家们发明汇编语言。

  • 假设10101010代表读取内存操作,内存地址是10101111,寄存器地址是11111010,那么完整的操作101010101010111111111010就代表读取某个内存地址的值并装载到寄存器,而汇编语言并没有改变这种操作方式,它只是二进制指令的映射:

1
2
3
4
5
LD:10101010  

id:10101111

R:11111010
  • 这样上述指令就可以表达为LD id R ,大大增强了代码的可读性。但是这样还不够友好,CPU只能执行三地址表达式,和人的思考方式、语言模式相距甚远。所以伟大的科学家们又发明了高级语言。

  • 高级语言之所以称之为“高级”,就是因为它更加符合我们的思维和阅读习惯。if…else这种语句看起来要比1010101010舒服的多了。但是计算机并不能直接执行高级语言,所以还需要把高级语言转化为汇编语言/机器指令才能执行。这个过程就是编译

二、语言发展历程

机器语言

计算机的硬件作为一种电路元件,它的输出和输入只能是有电或者没电,也就是所说的高电平和低电平,所以计算机传递的数据是由“0” 和“1”组成的二进制数,所以说二进制的语言是计算机语言的本质。计算机发明之初,人们为了去控制计算机完成自己的任务或者项目,只能去编写“0”、“ 1”这样的二进制数字串去控制电脑,其实就是控制计算机硬件的高低电平或通路开路,这种语言就是机器语言

汇编语言

机器语言作为一种编程语言, 灵活性较差可阅读性也很差,为了减轻机器语言带给软件工程师的不适应,人们对机器语言进行了升级和改进:用一些容易理解和记忆的字母,单词来代替一个特定的指令。通过这种方法,人们很容易去阅读 已经完成的程序或者理解程序正在执行的功能,对现有程序的bug修复以及运营维护都变得更加简单方便,这种语言就是我们所说的汇编语言, 即第二代计算机语言。

高级语言

而高级语言又主要是相对于汇编语言而言的,它是较接近自然语言和数学公式的编程,基本脱离了机器的硬件系统,用人们更易理解的方式编写程序。编写的程序称之为源程序 ,高级语言并不是特指的某一种具体的语言,而是包括很多编程语言,如流行的javacc++C#pascalpythonlispprologFoxPro易语言,中文版的C语言等等,这些语言的语法、命令格式都不相同。

三、语言区别

  • JavaScript毫无疑问是高级语言,所以它肯定是需要编译后才能执行。但为什么我们又称之为解释型语言呢?它和编译型语言、半解释半编译型语言又有什么区别呢?先从编译说起。

  • 编译:之前我们已经了解编译的概念,下面我们来聊聊平台:同样一份C++代码在Windows上会编译成.obj文件,而在Linux上则生成.o文件,两者不能通用。这是因为一个可执行文件除了代码外还需要操作系统 API、内存、线程、进程等系统资源,而不同的操作系统其实现也不尽相同。比如我们熟悉的I/O多路复用(事件驱动的灵魂),在Windows上的实现方案是IOCP方案,在Linux上是epoll。所以针对不同的平台,编译型语言需要分别编译,甚至需要分别编写,而且生成的可执行文件其格式并不相同。

  • 跨平台:Java通过引入字节码实现了跨平台运行:无论是在什么操作系统上.java文件编译出的都是.class文件(这就是字节码文件,一种中间形态的目标代码)。然后Java对不同的系统提供不同的Java虚拟机用于解释执行字节码文件。解释执行并不生成目标代码,但其最终还是要转为汇编/二进制指令来给计算机执行的。Java采用半解释半编译的好处就是大大提升了开发效率,然而相应的则降低了代码的执行效率,毕竟虚拟机是有性能损失的

  • 解释执行:JavaScript则更进一步。它是完全的解释执行,或者叫做即时编译。它不会有中间代码生成,也不会有目标代码生成。这个过程通常由宿主环境(如浏览器、Node.js)包办

四、编译过程

JavaScript 执行过程分为两个阶段,编译阶段和执行阶段。

  • 在编译阶段 JS 引擎主要做了三件事:词法分析、语法分析和代码生成

  • 编译完成后 JS 引擎开始创建执行上下文(JavaScript 代码运行的环境),并执行 JS 代码。

  • 对于常见编译型语言(例如:Java )来说,编译步骤分为:词法分析 -> 语法分析 -> 语义检查 -> 代码优化和字节码生成。

  • 对于解释型语言(例如:JavaScript )来说,编译阶通过词法分析 -> 语法分析 -> 代码生成,就可以解释并执行代码了。

词法分析

JS 引擎会将我们写的代码当成字符串分解成词法单元(token)。例如,var a = 2 ,这段程序会被分解成:“var、a、=、2、;” 五个 token 。每个词法单元token不可再分割。可以在这个网站地址查看 token :esprima.org/demo/parse.…

1
var a = 6;

mark

词法分析后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "a"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Numeric",
"value": "6"
},
{
"type": "Punctuator",
"value": ";"
}
]

f

1
2
3
4
function add(a,b) {
return a + b
}
add(3,4)

词法分析后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
{
"type": "Keyword",
"value": "function"
},
{
"type": "Identifier",
"value": "add"
},
{
"type": "Punctuator",
"value": "("
},
{
"type": "Identifier",
"value": "a"
},
{
"type": "Punctuator",
"value": ","
},
{
"type": "Identifier",
"value": "b"
},
{
"type": "Punctuator",
"value": ")"
},
{
"type": "Punctuator",
"value": "{"
},
{
"type": "Keyword",
"value": "return"
},
{
"type": "Identifier",
"value": "a"
},
{
"type": "Punctuator",
"value": "+"
},
{
"type": "Identifier",
"value": "b"
},
{
"type": "Punctuator",
"value": "}"
},
{
"type": "Identifier",
"value": "add"
},
{
"type": "Punctuator",
"value": "("
},
{
"type": "Numeric",
"value": "3"
},
{
"type": "Punctuator",
"value": ","
},
{
"type": "Numeric",
"value": "4"
},
{
"type": "Punctuator",
"value": ")"
}
]

语法分析

语法分析阶段会将词法单元流(数组),也就是上面所说的token, 转换成树状结构的 “抽象语法树(AST)”

1
var answer = 6 ;

语法分析后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "Literal",
"value": 6,
"raw": "6"
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}

代码生成

将AST转换为可执行代码的过程称为代码生成,因为计算机只能识别机器指令,需要通过某种方法将 var a = 2; 的 AST 转化为一组机器指令,用来创建 a 的变量(包括分配内存),并将值存储在 a 中。