在这个重学系列的课程中,都会假设大家对 JavaScript、CSS、HTML 有了一定的了解。而这个重学的过程其实是帮助我们在这些过去的知识里面建立一个新的秩序,也就是建立知识体系的过程。在重学 JavaScript 的过程将会带着大家以 JavaScript 的语法为线索,从细到粗的跟大家完整学习一遍 JavaScript 的语言知识
首先这里我们先讲一讲泛用的语言的分类学,在我们平时说话,我们讲的是中文,当我们去国外留学或者旅游,我们都会需要讲英文。不知道大家有没有这种经历,在国外时因为英文不是很好的时候,我们会把关键词凑起来这么一说,然后语法也不对,但是老外也听懂了。比如说 “很久不见”,我们就会说 "long time no see",然后老外还觉得挺好用的,所有他们也就加语言里面了。
这类的语言有一个特点,就是它的语法没有一个严格的定义,所以我们叫它做 “非形式化语言”,典型的代表就是我们平时说的这些。
在计算机里面,大部分的语言都是 “形式语言” —— 形式语言它的特性是有一个形式化定义,它是非常的严谨严格。
然后在形式语言里面也是分类的,这里给大家讲一下其中一种就是 “乔姆斯基谱系”。
乔姆斯基谱系:是计算机科学中刻画形式文法表达能力的一个分类谱系,是由诺姆·乔姆斯基于 1956 年提出的。它包括四个层次.
在乔姆斯基谱系里面 0123 是一种包含关系,就是说一个上下文相关文法,它一定也属于 0-型,但是反过来就不一定了。
<
, >
)括起来的名称来表示语法结构名*
表示重复多次|
表示 “或”+
表示至少一次我们来用 BNF 来表述一下大家比较熟悉的四则运算。
+
、-
、*
、/
小时候我们学的四则运算是加减乘除,实际上它是有一个优先级的关系的。我们可以理解为一个 1+2x3
的连加法当中,可以拆分成一个 1
和 2x3
组成的。那么 2x3
是它的子结构,然后 2
和 3
,就是这个结构中的 Number
,然后中间就是运算符 *
。
所以用 BNF 去描述这个远算的时候,首先我们会定义一个加法表达式
,格式就是:
乘法表达式的列表
或加法表达式
+ 乘法表达式
或加法表达式
- 乘法表达式
因为 BNF 是可以递归的,所以在定义表达式的时候,可以使用自身的表达式。
那么乘法也是类似,只不过那加法中乘法的表达式换成了 Number
就可以了:
Number
或乘法表达式
* Number
或乘法表达式
/ Number
最后我们看看用代码是怎么写的:
<MultiplicativeExpression>::=<Number> |
<MultiplicativeExpression> "*" <Number> |
<MultiplicativeExpression> "/" <Number> |
<AddtiveExpression>::=<MultiplicativeExpression> |
<AddtiveExpression> "+" <MultiplicativeExpression> |
<AddtiveExpression> "-" <MultiplicativeExpression> |
这里我们来尝试通过产生式,来深入理解一下前面讲到的乔姆斯基谱系。
终结符:最终在代码中出现的字符https://zh.wikipedia.org/wiki/ 終結符與非終結符
?
::=?
?
<A>?
::=?
<B>?
?
中写多个非终结符<A>
前面的 ?
就是上文,后面的 ?
就是下文?
<A>
一定是一个非终结符?
就是可以随便写,可以是一大堆终结符或者混合终结符和非终结符?
✅, <A>::=?<A> ❌那 JavaScript 是上下文相关文法,上下文无关文法还是正则无关文法?
JavaScript 总体上属于上下文无关文法,其中的表达式部分大部分属于正则文法,但是这里面是有两个特例的:
**
运算符,**
表示乘方get
get a {return 1}
那 get 就类似关键字的东西get
后面加入 :
,那 get 本身就是属性名了所以如果我们严格按照乔姆斯基谱系来理解,那么 JavaScript 是属于上下文相关文法。在 JavaScript 引擎的实现上,可以理解为众体的编程的结构,都是一个针对上下文无关文法的,一旦遇到像 get
这样的上下文相关的地方,那么就会单独的用代码做一些特例处理。所以一般来说也就不会把 JavaScript 归类为上下文相关文法去处理。
除了乔姆斯基谱系可以用 BNF 来定义,其实还有很多的不同的产生式的类型。比如说后来出现的 EBNF、ABNF,都是针对 BNF 的基础上做了语法上的扩张。所以一般来说每一个语言的标准里面,都会自定义一个产生式的书写方式。
比如说 JavaScript 中也是:
AdditiveExpression:
MultiplicativeExpression
AdditiveExpression +
MultiplicativeExpression
AdditiveExpression -
MultiplicativeExpression
它的开头是用缩进来表示的,就是相当于产生式左边的非终结符,非终结符之后跟着一个冒号,之后给了两个空格的缩进。然后在 JavaScript 的标准中,它的非终结符,加号、减号是用加粗的黑字体来表示终结符的。所以网上的产生式是五花八门的,只学一个 BNF 是无法读懂所有的语言的。虽然所他们都有不一样的标准和写法,但是它们所表达的意思大致上都是一样的。所以我们需要理解产生式背后的思路和原理,那么我们是可以忽略表达式上的区别的。
*
可能表达乘号或者指针,具体是哪个,取决于星号前面的标识符是否被声明为类型<
可能是小于号,也可能是 XML 直接量的开始,取决于当前位置是否可以接受XML直接量;tab
符和空格会根据上一行的行首空白以一定规则被处理成虚拟终结符 indent 或者 dedent;/
可能是除号,也可能是正则表达式开头,处理方式类似于 VB,字符串模版中也需要特殊处理 }
,还有自动插入分号规则;
语言的分类形式语言 —— 用途
形式语言 —— 表达方式
图灵完备性:在可计算性理论里,如果一系列操作数据的规则(如指令集、编程语言、细胞自动机)可以用来模拟单带图灵机,那么它是图灵完全的。这个词源于引入图灵机概念的数学家艾伦·图灵。虽然图灵机会受到储存能力的物理限制,图灵完全性通常指“具有无限存储能力的通用物理机器或编程语言”。
图灵机(Turing machine):又称确定型图灵机,是英国数学家艾伦·图灵于 1936 年提出的一种将人的计算行为抽象掉的数学逻辑机,其更抽象的意义为一种计算模型,可以看作等价于任何有限逻辑数学过程的终极强大逻辑机器。
JavaScript 这种解释执行的语言,它是没有 Compiletime 的。我们现在也会用到 Webpack 去 build 一下我们的代码,但是实际上还是没有 Compiletime 的。所以说,今天的 Runtime 和 Compiletime 的对应已经不准确了,但是我们依然会愿意沿用 Compiletime 的习惯,因为 JavaScript 它也是 “Compiletime(开发时)” 的一个时间,所以也会用 Compiletime 这个词来讲 JavaScript 里面的一些特性。
Array <Parent>
的地方都能用 Array <Child>
Function <Child>
的地方,都能用 Function <Parent>
一般来说我们的命令式语言可能有一些细微的结构上的不一致,但是它总体上来讲会分成5个层级。
我们对每一个层级的讲解方式都会有一个,比较固定的结构。对每一个层级来说我们是以语法作为线索,但是实际上除了语法,重点讲的是语义和进行时。
所谓 “语义” 就是在实行上在用户使用的时候是什么样子的。前端工程师最关心的就是,我们写什么样的语法,最后变成用户的电脑上运行时什么样子的,这是我们的变成过程。
而中间连接语法运行时,正是这个语言的语义。我们通过一定的语法表达一定的语义,最后改变了运行时的状态。