没有合适的资源?快使用搜索试试~ 我知道了~
JavaScript语言精髓与编程实践(第三章).pdf
需积分: 2 0 下载量 2 浏览量
2022-08-04
17:00:28
上传
评论
收藏 1.07MB PDF 举报
温馨提示
试读
86页
JavaScript语言精髓与编程实践(第三章).pdfJavaScript语言精髓与编程实践(第三章).pdfJavaScript语言精髓与编程实践(第三章).pdf
资源推荐
资源详情
资源评论
JavaScript 语言精髓与编程实践(第 2 版)
JavaScript 的非函数式语言特性
术语“命令式”(imperative)来自于命令和动作,这种计算模型就是基于基
础机器的一系列动作。
——《程序设计语言概念和结构》,Ravi Sethi
3.1 概述
源于对计算过程的认识的不同而产生了不同的计算模型,基于这些计算模型进行的
分类,是计算机语言的主要分类方式之一。在这种分类法中,一般将语言分为四大类:
命令式语言、函数式语言、逻辑式语言和面向对象程序设计语言。
本节将首先讨论程序的本质,并从这个本质出发,以另一种分类法对程序语言做出
分类:命令式语言和说明式语言。本章将基于该分类法讨论 JavaScript 的非函数式语言特
性,有关内容组织见表 3-1。
表 3-1 基于程序本质的分类
分类 子类 章节
冯·诺依曼(结构化编程) 第 3.2 节
命令式
面向对象(面向对象编程) 第 3.3~3.4 节
函数式 第 4 章
说明式
(其他)
CHAPTER
第
3
章
第 3 章 JavaScript 的非函数式语言特性
JavaScript 语言精髓与编程实践(第 2 版)
118
3.1.1 命令式语言与结构化编程
“命令式”这个词事实上过于学术化。简单地说,我们常见的编程语言,从“低级
的”汇编语言到“高级的”C++,以及我们常用的 Basic、Pascal 之类都是命令式语言。
命令式语言的演化分为“结构化编程”和“面向对象编程”两个阶段。无论是从语
言定义还是从数据抽象的发展来看,面向对象编程都是结构化编程的自然延伸。
结构化程序设计语言中,对结构的解释包括三个部分:程序的控制结构、组织结构
和数据结构。所谓控制结构,即是顺序、分支和循环这三种基本程序逻辑;所谓组织结
构,即是指表达式、语句行、语句块、过程、单元、包等;所谓数据结构,包括基本数
据结构和复合数据结构,且复合数据结构必然由基本数据结构按复合规则构成。
整个命令式语言的发展历程,都与“冯·诺依曼”计算机系统存在直接关系。这种
计算机系统以“存储”和“处理”为核心,而在编程语言中,前者被抽象成“内存”,
后者被抽象成“运算(指令或语句)”。所以,命令式语言的核心就在于“通过运算去改
变内存(中的数据)”。我们应该注意到:软件程序与硬件系统在本质上就存在如此亲密
的关系。
那么命令式语言与结构化编程在概念上有多大的相关性呢?事实上它们并不是同一
层面上的概念,前者讲的是运算范型(表达为语言),后者讲的是一种程序设计与开发的
方法。因此在结构化编程的整个知识域中,其实仅有“数据结构”与“命令式语言(这
一编程范型)”在同一层面上。而所谓“数据结构”,即是命令式语言所关注的“存储”。
由于命令式语言的实质是面向存储的编程,所以这一类语言比其他语言更加关注存
储的方式。在程序设计的经典法则“算法+数据结构=程序”中,命令式语言是首先关注
“结构”的,这里特指“数据结构(或类型系统)”。表 3-2 说明在 Intel 计算机体系中“数
据结构”上的简单抽象。
表 3-2 “数据结构”上的简单抽象
自然语义 机器系统 编程系统 语言/类型系统
基本数据单元 16/32/64 位系统 位、字节、字、双字 Bit、byte、word、dword...
连续数据块 连续存储块
数组、字符串、结构体
(*注 1)
array、string、struct...
有关系的数据片断 存储地址 指针、结构、引用 pointer、tree...
*注 1:C 语言中的“结构”类型在 Pascal 中称为“记录(record)”。为了避免与本章中所述的“算法+(数
据)结构”的结构混淆,在后文中,编程语言中的“结构”称为“结构体”。而“结构”一词,通常用来表达概
念上的“数据结构(或类型系统)”。
3.1 概述
JavaScript 语言精髓与编程实践(第 2 版)
119
命令式语言在运算上也基于上述的“存储结构”来进行算法设计。例如表检索,通
常认为是在一个“连续数据块”中找到指定的、一个“基本数据单元”中的值。例如:
/**
* programming language: javascript
* params:
* - key, a value. etc, type of byte
* - table, a array. etc, type of byteArray.
*/
function SearchInTable(key, table) {
for (var i=0; i<table.length; i++) {
if (table[i] == key) return true;
}
return false;
}
基于上例的基本需求和数据结构的设定,推论出“有序表检索效率更高”,并 进一 步
提出有表排序的相关算法(例如冒泡排序),设计出“二分法查找”等有序表检索算法。
再后来,算法从“对原始数据排序”进化到“对数据映射排序”,从而有了更快速的“Hash
排序”与“Hash 检索”。海量数据处理的原始模型才由此逐渐形成。而所有这些算法的
原始基础,仍旧是表 3-2 中对“数据表现形式”的设定。
像 Frederick P. Brooks 和 Jr.这样的先驱们,很早就意识到“算法+数据结构=程序”
的价值。Brooks 就在《人月神话》中指出“数据的表现形式是编程的根本”。正是大师们
在“数据的结构”上的不懈努力,成就了 C/Pascal 这样的结构化编程语言
1
,Windows、
Linux/UNIX 这些伟大的操作系统,以及 Oracle、MS SQL Server 等这些数据库系统
2
。
然而,从基于 x86 系统的汇编语言,到代表近三十年来“高级语言”发展史的 C、
Pascal、Basic,以及在关系数据库方面独领风骚的 SQL……所有这些在通用软件开发领
域耳熟能详的编程语言,都困守在“冯·诺依曼”体系之中,无数的经典语言与编程大
师谨遵“程序=算法+结构”这句断言,而从未在本质上出现过任何的突破。
在另一种分类体系中,SQL 被归类为“第四代程序设计语言(Fourth-Generation
Language,4GL)”。在该分类体系中,还包括机器语言(1GL)、汇编语言(2GL)、高
级语言(3GL),以及图形化程序设计语言(5GL)。这是一种较为笼统的以语言演化的次序、
功用及实现方式来分类的方法。
1
结构化程序设计中的“结构”并不是语言概念中的“结构类型(struct)”,二者没有必然的联系。结构化分析方法的要
点是根据数据的处理过程,自顶向下地分解系统模块。这一分析、设计的过程被称为结构化,它的产物是模块(module)、
过程(procedure)等之间的交互与接口,而不是一个具体的数据结构。从软件开发过程来讲,编程语言中的数据类型
(包括结构体等),来自于上述分析、设计阶段的数据建模。
2
结构化程序设计绝不是“数据结构”一言可概之的,但这里我们重在强调语言特性,而非编程方法的历史与演进。
第 3 章 JavaScript 的非函数式语言特性
JavaScript 语言精髓与编程实践(第 2 版)
120
3.1.2 结构化的疑难
在命令式语言发展上的所有努力,最终都必然面临的问题是“如何抽象数据存储”。
我们知道,在结构化编程时代,解决这个问题的是“结构体(结构类型)”。但是一方面,
结构体在数据表达上过度的弹性带来了编程设计中的不规范情况,因此事实上在结构化
编程时代,除了关系型数据库之外,并没有什么一致的、规范化的编程模型出现。另一
方面,结构体根本上是面向机器世界的“存储描述”,因此它的抽象层次明显过低。
抽象层次过低带来的问题至少包括三个方面。
其一,结构体与实体直接相关,并且将这种相关性直接呈现在使用者的面前,因此
开发人员必须面临数据的具体含义与关系。
在命令式语言中,变量(数据)的作用域首先按冯·诺依曼体系分为数据域与代码
域,然后根据编译器的约定,分为局部域、单元域与全局域。一些编译器也约定了“块”
级别的作用域,例如 C 语言中的线程锁机制。
然而,结构体本身并不具有隐藏数据域的特性,它只是忠实地反映程序系统与实际
应用环境的映射关系。例如一个对房间的描述:
(**
* programming language: pascal
*)
TRoom = record
bed: integer;
desk: integer;
chair: integer;
lamp: integer;
window: integer;
people: integer;
// reserved : array [0..300] of byte;
end;
我们假设将
TRoom
这个结构体应用于一个实际系统中:对于工程辅助设计(CAD)
系统来说,
people
成员显然是多余的;而对于实境系统(例如导游)来说,
people
又是
主要的成员,其他的则可能由另一个封闭的子系统处理。因此,很直接的问题是,对于
更复杂的系统来说,需要更多的、更复杂的“实体与成员”的包含或封装关系。换而言
之,数据对于不同的子系统、结构体和逻辑代码来说,应该存在不同的可见性。
在结构化时代,处理这个问题的方法是在 SDK 中约定“带下划线(_)前缀的成员
是保留的”,或者直接隐匿掉这些成员的名字,并从文档中彻底清除它们(如上例中的
reserved
成员)。这些做法,除了激发程序员们探索不止的欲望,以最终写出《某某系
统未公开文档技术大全》之类的著作之外,并未解决根本问题。
其二,结构体的抽象更面向于数据存储形式的表达和算法实现的方式,脱离了具体
3.1 概述
JavaScript 语言精髓与编程实践(第 2 版)
121
使用环境,算法的结构也缺乏通用性。
这其实是一个非常致命的问题。因为大多数情况下,结构一旦设定,算法也就确定
了。例如对 zip 文件的文件头的描述:
/**
* programming language: pascal
*/
TCommonFileHeader = packed record
VersionNeededToExtract: WORD; // 2 bytes
GeneralPurposeBitFlag: WORD; // 2 bytes
CompressionMethod: WORD; // 2 bytes
LastModFileTimeDate: DWORD; // 4 bytes
Crc32: DWORD; // 4 bytes
CompressedSize: DWORD; // 4 bytes
UncompressedSize: DWORD; // 4 bytes
FilenameLength: WORD; // 2 bytes
ExtraFieldLength: WORD; // 2 bytes
end;
TLocalFile = packed record
LocalFileHeaderSignature: DWORD; // 4 bytes (0x04034b50)
CommonFileHeader: TCommonFileHeader; // 26 bytes
filename: AnsiString; // variable size
extrafield: AnsiString; // variable size
CompressedData: AnsiString; // variable size
end;
这个结构体的设计中,
TLocalFile
是作为文件头被写入 zip 文件的每一个子文件的
压缩数据的头部的,其中前 30 个字节可以作为一个完整的数据块直接保存。但是,
TCommonFileHeader
的设计中,
Crc32
和
CompressedSize
这两个成员却需要在完成数据
压缩之后才能写入。也就是说,在做 zip 压缩文件时,要在添加完一个文件的压缩数据
后,将文件读写指针移回到这个位置来重写这两个值。
结构的设计就决定了算法的实现,这已然是很明显的事。现在所有的 zip 文件都以
这种方式标识着子文件,因此我们已经没有任何办法来修改算法,使结构被重用到新的
算法,或者使其他算法被应用到这个旧的结构中。
结构体的设计直接面向存储,正是这种过低的抽象层次使重用性大大地降低。程序、
系统和开发人员被约束在结构的设计与调整之上,而不是关注于现实系统的实现之上。
其三,僵化的类型与僵化的逻辑并存,影响了业务逻辑的表达。
现实生活中,人们并不关心“关注对象”的类型,而只关注于具体的逻辑。例如人
们在饥饿时只关注“吃”,并不关注于吃的是什么。
在一个子系统的逻辑产生时,子系统事实上只关注逻辑作用于一个该作用的对象,
而并不关注这个对象的构造(如类型)。例如财务人员面对手中的一堆票据,他只关心这
些票据的总金额是多少,因此“求总计”的子系统最直接的实现方法就应当类似于“财
剩余85页未读,继续阅读
资源评论
aypatam_cto
- 粉丝: 0
- 资源: 19
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功