JavaScript语言的结构化

程序是可被组织的元素。

然而如果程序是可被组织的,那么“结构化”其实就只是组织的手法之一,这意味着后者——结构化——只是“程序是什么”的一个解,而绝非唯一解

4.1 概述

对计算过程的认识不同产生了不同的计算模型,基于这些计算模型进行的分类,是计算机语言的主要分类方式之一。这种分类法中,一般将语言分为四大类:命令式语言、函数式语言、逻辑式语言、面向对象程序设计语言。如果从程序本质的角度来看分为两类:命令式语言和说明式语言

结构化是命令式语言的主要实现手段

命令式语言的核心就在于“通过运算去改变内存(中的数据)”

面向对象语言式结构化编程的延申

进一步抽象:接口

4.2 基本的组织元素

4.2.1 标识符

总是可以将源代码文本视为由空白字符等隔开的词法记号(tokens)序列。

在Tokens中。由语言预设的标点符号和部分用作保留字的标识符名是确定了书写格式的,例如:++、if和function等,此外都可以由用户代码来定义书写,包括标识符、字面量和模板

尽管书写的语法不同,但总的来说,这三种代码定义的Tokens都是一系列字符的有序书写,只是:

  • 标识符只表明一个名字
  • 字面量表明由字面含义决定的值
  • 模板表明一个可计算结果的字符串值

4.2.2 表达式

表达式是0-1个运算符和至少一个操作数的有序书写

事实上表达的是:名字和值之间进行计算,并返回名字与值。

,连续运算符,可以连续运算,返回最后一个有效值

JS概念中,表达式不能独立语句存在。即使单个表达式,可以被称为表达式语句。

4.2.2.1 字面量

数组、对象、正则表达式等

4.2.2.2 初始器

var x = [1] // 初始器
var x = 1 // 字面量

ECMAScript希望在字面量表示中不包含运算过程,而初始器是可以包含运算过程的。所以ES5中只明确了5中字面量语法,这些字面量在编译期就确知的:null、true\false、数值、字符串、正则表达式。数组和对象就必须是一个运算的结果

本书也将初始器称为“字面量风格的”,在这种情况主要是强调它们是独立的、原子的、可参与计算的。

受到这一约定叙述风格影响的语法元素。都是有着自己的运算过程,但又表现为字面量风格的值的。包括数组、对象、函数、正则、模板

4.2.3 语句

标识符表达的名字,字面量和模板表达的是值,这些Token最后有两个演进的方向

  • 是在表达式通过名字来引用值,并进一步的做值运算。这即是JS中函数式语言特性的由来
  • 通过语句来串联名字和值,并最终表述为将值置于名字。这既是JS中命令式(含过程式和面向对象的)语言特性的由来

JS语句分为声明语句和非声明语句两类,所有的声明语句都是静态词法分析的,而非声明语句则是动态执行的(即用来叙述计算逻辑的)

对声明语句来说,它要么用于声明标识符名字,要么用于声明名字与值得关系,后者称为绑定

所谓函数本质上也只是在语句(甚至是表达式或者标识符)这个层面得组织元素,它是典型得直接绑定名字和值得语法结构

4.2.4 模块

模块的出现并不是解决具体编程能力的问题

模块在语义上的成功之处,在于它是平衡“对功能的展示和对细节的屏蔽”两种需求所能带来的最小可能结果

4.2.5 组织的原则

事实上这些原则可以只包括三部分。即:逻辑的、值得和形式结构的,前两者用于约束一个最小可计算系统,后者用于让这个计算系统在形式上具有确定性

4.2.5.1 原则一:抑制数据的可变性

数据得可变性称为状态。如果系统是无状态的,那么该系统存在两种可能,一种是绝对静态,因为没有可变性而没有状态,另一种是绝对连续动态的,因而没有有意义的瞬时状态。

Promise为核心的并行语言特性,实际上就是一个三状态数据的可编程系统,另一方面JS建议使用let/const来代替var本质上就是在控制数据的可变性(作用域),以便达到在该范围内减少状态总数的目的

4.2.5.2 原则二:最小逻辑和最大复用

将尽可能多且明确的逻辑内聚,以得到更大粒度的复用单元。仍然是Promise和Generator,它们在向外暴露明确的一致性的接口的同时,是将其内部封装的语义和逻辑整体作为复用单元的。

4.2.5.3 原则三:语法在形式上的清晰与语义一致性

从根本上来说Promise的语法存在一个及其致命的问题,.catch()借用了try...catch的语义,但是触发的主体并不相同,.catch是上一个promise,例如.then,而并不是最初的promise,这一点和触发try...catch的触发是在try语句块中有很大的不同,这两个逻辑存在“异常发生者与发出的位置”的不一致,于是给使用者带来了困扰

4.3 声明

声明和声明式编程是无关的,是不同的概念

JS的所有声明都针对标识符名字,以表明改名字在三个方面的性质:标识、值和确定性

4.3.1 声明名字

所有声明语句都至少包括一个名字的声明

在声明是名字不绑定值得,称为后绑定

同时声明名字和值得时候,具体得绑定操作是在JS引擎在一个“置值过程”中完成的

一些名字声明和绑定过程是同时发生并交由引擎在“初始化环境”时完成得。这个过程对用户代码是不可见的,例如函数声明

4.3.2 确定性

JS中除了const和namespace之外得所有语句声明得名字都是使用MutableBinding来创建的,因此我们总是可以重写标识符。这是JS动态语言特性的体现

然而函数表达式式字面量风格的值,而非语句,对于此,JS约定,所有类型的函数表达式——在它作为独立语法元素(表达式的操作数)的环境中,是不可置值得

function foo() {
  foo = 100;
  console.log(foo) // 100
}
foo() 

(function foo() {
  foo = 100;
  console.log(foo) // [Function: foo]
})();

4.3.3 顶层声明

JS代码中的顶层声明是被特殊处理的,所谓顶层声明是指一个块中所有声明语句或其第一层具名函数中的声明。

顶层声明包括“顶层词法声明”和“顶层变量声明”两种。

主要就是var的变量提升的问题,函数和var同理

4.4 语句与代码分块

排除所有用于声明的语句之后,剩下的所有非声明语句,都是过程叙述性质的

  • 陈述一个过程
    • 陈述将被组织的元素
    • 陈述上述元素之间的结构方法
  • 表达经过上述陈述语句陈述过程之后的结果值

4.4.1 块

如果将一段代码理解为一个形式上的块(x),那么块x中除去简单语句(它们不构成自己的形式分块),其他部分也是分块的:

  • 简单语句是该块x的成员
  • 其他分块是子集的块

4.4.1.1 简单语句

可以将大多数表达式语句、空语句和debugger语句等归为一类,称为简单语句

简单语句自身并不构成代码的形式分块

4.4.1.2 单值表达式

一个块意味着一个独立的计算环境,可以有自己的内部标识符列表等,这可以理解为对“结构化编程”中信息隐蔽的体现。由于在概念上,表达式本身的计算环境依赖于它所在的语句,因此表达式本身是不需要分块的。进一步的,表达式作为简单语句也是没有分块的

但是JS中的值可以构成单值表达式,并进一步构成单值表达式语句,因为单值中的“字面量风格的值”本身是需要一个独立的计算过程与环境的,但是即便如此,这些单值表达式所在的“简单语句本身”也是没有对应形式的分块的

4.4.2 块与语句的语法结构

多数语句都能用于组织逻辑,这包括全部的循环、分支和多重分支语句,以及各种控制结构子句、异常语句等。这些语句许多都是分块的,并且支持在块中包括更多的执行逻辑,以及更多子级的块。

4.4.2.1 语义上的代码分块

JS的语法设计中的块语句和with语句相对比较特殊,这在于它们本身的语义就是用于描述分块形式的

块语句{}在语义上就是顺序地组织一批语句,因此它可以直接描述三种基本逻辑中的顺序逻辑,并且——在语法性质上——它的整个块仍然是“一个”语句

4.4.2.2 分支逻辑中的代码分块

我们通常是利用“块语句是一个语句”的性质来得到一个形式分块,并“自然的”形成常见的代码风格的

但是if语句本身并没有一个代码的形式分块。因为语法设计上不必要

一般if语句的形式分块是由{}创建的,并不是if语句自有的

4.4.2.3 多重分支逻辑中的代码分块

多重分支具有一个形式分块,并且所有分支都共享这个自有的块

可以在各分支中使用自己独立的块语句和let/const声明,或者使用switch语句外部的声明

4.4.2.4 循环逻辑中的代码分块

只有在循环语句的循环条件中使用let/const声明了新名字时,才会存在代码分块

包括for…of、for await …of和for…in等在内的所有for循环,只要在循环条件中使用let/const声明了新名字时,那么就具有一个自有的形式分块,否则都没有这一特性

更进一步while/do…while循环也都不具备这样的特性,因为它们不具备“声明名字”的语义

使用let声明,每次循环体回创建一个新的环境避免重复声明的问题,所以如果不是循环体内具有并行机制,建议不要使用let/const

4.4.2.5 异常中的代码分块

在try语句的语法中,所有{}都是标准的块语句语法的形式分块,由于catch\finally块必须至少存在一个,所以try语句拥有的形式分块大概有两到三个

每个形式分块都是独立执行的,与其他块无关,因此可以在不用的块使用let声明相同的名字

4.4.3 块与声明语句

4.4.3.1 只能在块中进行数据声明

while(false) let i = 0;

因为JS引擎无法在while语句的相应位置初始化一个用于注册let/const所声明名字的环境,因此在所有的单语句中都不能使用let/const来进行数据声明

4.4.3.2 能同时声明块的声明语句

语句可以在声明变量名的同时声明一个形式分块,例如函数声明(语句)

于此类似import语句也具备这一性质

函数所声明的形式分块包括它的函数名和参数名,并且它的函数名将会在“所在的块”中进行一次等级,而模块所声明的形式分块则另据特殊性,因为模块的作用机制约定——同一模块的多次import之间会共享同一个块(以及其环境),因此事实上它的形式分块不是由import语句来创建的,而是由JS引擎在装配模块的过程中创建的

函数表达式和函数声明语句的主要不同,在于后者会将在“所在的块”中登记函数名,这也是不存在“声明匿名函数的语句”这样的语法的原因

4.4.3.3 声明语句与块的组织

更多的函数、语句与模块构成了JS文件

在Es6之前JS还没有模块和程序的概念,而是所有的块都视为有顺序关系的代码片段,这个时期的JS并没有约定块何时以及如何装载的引擎中,因此引擎通常对载入的每个代码先做语法分析,而后从第一条语句开始执行——即使这条语句看起来并不合理,JS进行不了有效的、可预期得分析

但从es6开始,JS提出了模块的概念,这些模块通常是拓展名为.js的JS文件,此时:

  • 所有声明语句,要么是在声明标识符,要么就是在声明块
  • JS约定所有的声明都必须在语法分析期处理


语言   js      js 基础 绿宝书

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!