如何掌握一门编程语言

       软件开发需要使用计算机编程语言,目前编程语言很多,不同语言擅长不同的工作,客观要求开发人员掌握多种语言,这就要求开发人员能够熟练使用不同的编程语言。

计算机编程语言,以下简称编程语言。

       虽然编程语言存在的目的是同计算机打交道,但是不同的编程语言对计算的抽象各有不同,比如:Java认为万物皆是类,而C认为函数才是王道,有的语言可以操纵计算机底层硬件,有的语言却喜欢呆在浏览器中。不同编程语言的差别堪比不同国家所用的人类自然语言,如果对需要对编程语言掌握不停留在helloworld的水平上,都需要付出经年累月的时间成本才可以熟练掌握。掌握多门编程语言其实对开发者理解计算机环境,解决问题的模式,以及反思自己熟知的编程语言都有很大的裨益。

       既然需要掌握多门编程语言,又不想毫无价值的浅尝则止,是否有一种高效学习编程语言的方法,以及用来检验自己是否熟练掌握的工具(或方法)?

       声明一个抽象的编程语言,简称:APL,定义APL解决问题的功能特性,通过枚举出APL对问题的解法就可以勾画出编程语言的特性列表,通过这个特性列表就能够快速学习其他的编程语言,并利用APL特性列表进行查漏补缺。接下来,首先明确APL的主要特点以及功能列表,然后通过一个具体编程语言(JavaScript)来与APL进行生态化反。

APL的主要特点

       对于APL而言,描述问题,接收问题以及解决问题是其主要工作。描述问题需要依靠开发者以及APL提供的语意元素,编程语言根据所处领域的不同,提供了不同的抽象,比如:面向通用领域的Java,面向数据库的SQL,面向页面的HTML和面向统计分析的R,开发者首先要分析问题,结合问题所属的领域,结合相应的编程语言抽象,实现问题在编程语言上的投影(或者建模)。接收问题是编程语言输出的产出物运行在操作系统上,通过接收问题的输入,调用解决问题,并将输出返回给使用者。解决问题是开发者使用编程语言,在受限环境(也就是编程语言和问题描述)下,组织代码逻辑来实现功能,这就是解答的部分,解决问题的逻辑会有改变,如何复用以及低成本扩展是考验开发者逻辑组织能力的点,当然有些改动也会引起对问题描述的重新定义。

       APL由类型、行为和运行系统组成,三个子系统在软件生命周期中都有参与,且发挥着不同的作用,从软件视角看APL,其关系如下图所示:

       如上图所示,用户和开发者对于需求的理解会产生软件设计,它包含了软件的数据结构和用例行为,它们都会被开发者使用的APL分拆到类型、行为和运行系统中。当开发者部署好使用APL编写的软件,用户通过硬件外设使用产品,输入和输出透过驱动与内核交互,而内核将输入给到运行系统,在行为系统和类型系统的共同作用下,得到计算结果,再顺着内核和驱动输出到硬件,用户感知到输出结果,看起来挺不错。

       描述问题依赖于APL的类型系统,包括:如何定义变量,有哪些内建的类型,特别是字符串的定义以及功能是否强大,如何自定义类型,类型与对象的关系,以及对象的生命周期,是否有抽象数据类型的支持。

       接收问题取决于APL的运行系统,包括:是否有虚拟机,运行环境以及事件机制,是否具备定时器,输入和输出是如何处理的,网络编程支持是否足够强大,对于多线程和并发的支持是否完备。

       解决问题凭借于APL的行为系统,包括:如何定义函数,类型、对象以及函数的关系,多态和重载如何实现,函数是否是一等公民,是否支持高阶函数,顺序、条件与循环的表现形式,异常机制是否完备。

APL的功能列表

       从类型、运行和行为系统三个方面分别描述APL的功能。

APL的类型系统

       类型系统是为了对问题领域进行精准的定义和描述,其存在目的和发展方向都是在不断的增强其表述能力。类型系统的功能主要包括以下:

编号 名称 描述
T1 定义变量 使用变量描述一个事物,一般来说定义变量是APL最为基础的语句,它会涉及到关键字、变量名以及背后的内存分配。
T2 内建的类型 现实世界中的问题是连续的,而计算机存储系统是离散的,后者有一定的局限性,而类型就是编程语言映射两个世界之间的抽象。现实世界中的事物没有所谓类型,但是计算机按照byte进行数据存取,要将二者关联,就需要一定的抽象,比如:整型、浮点型或者字符型等类型,它们可能占据不同的byte数,用这些类型来表达现实世界。
APL会提供内建的类型,一般包含4类数值型:布尔、字符、整型和浮点型,还会提供类似指向对象的引用型,其内容也可能是整型,记录着一块内存地址编号。
T3 字符串的定义以及功能 字符串是最常用的功能,APL会提供字符串的创建和相关操作,不同语言对于字符串有不同的定义,有的视为一种数组,有的作为基础类型。字符串的操作要注意是否返回新的引用,也就是APL对字符串视作可变还是不可变的,同时子串的获取以及字符串的拼接是APL需要提供的基础功能。
T4 自定义类型 APL提供关键字或表达式来创建自定义类型,自定义类型用来实现对现实世界的抽象,或者说对问题的抽象。自定义类型可以通过组合、继承等形式来反映问题的复杂性,同时也能用来描述计算机中常用的数据结构:线性表、链表、栈、树或者有向图等。
T5 类型与对象的关系 APL支持从类型创建出对象,对象一般称为实例,它所在的内存位置一般是堆。对象和类型之间一般存在关系,可以通过对象获得其类型,不同的编程语言提供了不同的方式,有强有弱。在函数调用时,可以允许传入的对象是形参类型的子类型实例,也就是符合里氏替换原则。
T6 对象的生命周期 对象实例从创建到销毁是有一套标准或者约定的。如果对象作为局部变量,在函数体中完成分配,当函数执行完毕,对象也随之终结,当然对象对应的内存也会被回收。创建和销毁是比较粗的阶段,APL还有可能会提供类似初始化和销毁前的生命周期阶段,在不同的阶段,对象中的属性会有不同的值,对象中的某些方法会被触发调用。
T7 抽象数据类型的支持 APL一般会提供类似数组的结构对多个对象进行容器化管理,数组一般是连续的空间,可以提供对其中元素进行遍历、查询以及修改的功能,不同的编程语言对于数组的功能定义有区别,大部分是不可变的, 目的是保证数组内存的连续性。除此之外,APL会提供类似列表、链表或Map等比较经典的数据类型用来支持开发者构建更复杂的类型结构,不同的编程语言的数据结构标准库有强弱之分。

       对于APL的类型系统而言,从T1到T7是一个逐步演进的过程,也是一个逐步复杂化的过程,因为只有变得复杂,才能解决复杂的问题,它们之间的关系和解决问题如下图所示:

       从T1到T3,解决的是APL如何像工具般的为人所用,可以使用APL去面向过程的方式解决一些小问题。T4的目的是描述问题,让问题以更为自然的形式呈现在系统中,使用面向对象的方式来构建更大规模和更易维护的程序,这对APL本身而言没有多少难度,但对于开发者来说,这是一个修行的过程,有太多的经验需要积累。开发者在使用面向对象技术过程中,会对APL有更具体的需求,比如:需要把控类型和对象的创建,以及生命周期,这就要求T5到T7被支持。虽然不同语言的解决方案有所不同,但思路都差不多,因为人们解决问题的思路其实都很相似,所以在上百种编程语言中,这部分都是高度相似的。

APL的运行系统

       运行系统对接现有计算机体系,能够通过系统调用驱动硬件,让程序具有输入和输出的能力。对接现有计算机体系在于能够以进程的形式运行在现有操作系统上,能够通过系统调用调度和驱动硬件,包括:处理器、键盘鼠标、显示器以及网卡多种硬件,系统调用被APL所隐藏,运行系统的功能主要包括以下:

编号 名称 描述
R1 虚拟机与运行环境 APL是否是一种托管类型的语言,如果是,它就拥有一个运行时环境,开发者编写的代码只能由该语言的运行时来解释执行,虽然它的存在不影响你编写程序,但是对它的掌握是必须的。随着对运行时环境掌握的越发全面,开发者就能更好的调整程序运行的性能。虚拟机的存在就需要编程语言开发者对虚拟机配置有更多了解。
非托管类型的语言,编译器需要理解当前操作系统的系统调用API,在编译时将代码中的某些调用与系统调用进行绑定,在链接时完成输出物与当前操作系统融合,最终输出可执行文件。托管类型语言的运行时就是一个可执行文件,它自身已经和当前操作系统绑定完成,能力边界已经确定,只是用来解释开发者编写的代码。相比底层能力的潜力,非托管类型的语言要强于托管类型的语言,因为对系统调用的API由开发者自己负责。
R2 事件机制 事件机制包含了事件定义和事件处理器。
APL定义事件,让开发者的代码能够感知到环境的变化,它表示发生了什么事件。不同类型的编程语言对事件机制的侧重不一样,比如:系统类型的编程语言,事件定义一般比较原生且种类少,因为它会观测系统层面的事件,而偏向应用的编程语言,比如:JavaScript,则对事件有非常丰富的支持,用户在浏览器上的任何操作都会以事件的形式暴露出来。
APL提供事件处理器的抽象,接受事件实例然后进行处理,会涉及到事件处理线程,一般称为事件处理模型,其实也就是编程语言对事件处理实现时的一些约定。
R3 定时器 APL透过操作系统将定时执行和调度能力暴露给开发者,一般来说这是一个小功能,但是它往往会和APL的并发模型有一定交互。与事件机制一样,需要注意执行定时任务的处理线程,也需要遵循具体编程语言的最佳实践。
R4 输入和输出 APL透过操作系统具备了标准输入输出流以及错误输出流,这三个流是每个进程都拥有的公共服务。虽然支持输入输出,但是APL是否对输入和输出做出了完整抽象,如何创建输入输出流,输入流来自操作系统内核态将读入的数据放入到用户态,输出流通过输出到用户态的内存中,交由系统调用将内容拷贝到内核态或者通过DMA完成数据写出。
如何能够通过输入流读入一个字节,如何缓存输出的内容,并且在到达一定量后再输出到流中,这些繁重的工作在系统级语言会有良好的抽象。偏向应用的编程语言这方面比较弱,比如:JavaScript,在ECMAScript中,甚至没有对输入输出流的抽象。优雅且高效的输入与输出抽象,是高性能服务端编程语言的必备。
R5 网络编程 APL一般可以触及到传输层和网络层,网络编程会和输入输出结合起来进行工作。网络编程的本质是面向协议编程,具体的编程语言可以提供支持HTTP协议的工具,也可以选择提供面向传输层TCP的字节流抽象,后者需要实现面向网络分层的大量抽象,但也只有后者更擅长构建网络服务。
R6 多线程支持 现代多处理器环境下,APL会提供对进程、线程以及纤程的抽象,当然也会对异步计算进行抽象,否则就无法利用异步计算得到的结果。APL如何创建一个轻量级进程,开发者基于APL的异步计算抽象实现具体的异步计算,当异步计算完成后如何能够获取到异步计算的结果,这是APL对多线程支持最起码的要求。
至于如何取消执行中的异步计算,如何能够合并异步计算结果,如何将不同的异步计算交由不同的处理器执行,这就要看APL对多线程支持的完善度了。

       对于APL的运行系统而言,R1到R6存在的目的是将操作系统的能力透过运行系统投放给开发者,同时也将开发者对于运行系统的使用提交给操作系统。

       R1关系到APL的内存模型设计,是APL如何解决存储(或者内存)和计算问题的关键。通过R2和R3,可以让程序能够响应外部事件或者定时的完成某些工作,一个属于编写监听器被动处理,另一个是定时主动执行。如果是服务端编程语言,需要特别注意R4,如何能够编写高效的I/O处理程序是高性能的关键,同样R5是APL解决网络,使程序从单机演变为分布式的基础,在此基础上R6的支持就显得非常必要了。

APL的行为系统

       开发者需要使用APL的行为系统来实现逻辑,类型系统定义了程序如何描述问题,行为系统定义了程序如何与外界交互,而行为系统的目的就是让程序按照开发者的解题思路跑起来解决问题。只有运行起来的程序才能够来达成开发者的目的,这点是至关重要的,而在运行中需要面对不少问题,行为系统的功能主要包括以下:

编号 名称 描述
B1 定义函数 APL的函数由函数名、参数(或者形参)、返回值以及函数体组成,具体的编程语言对于函数定义有不同的表现方式,但基本要素和APL差不多,主要区别在于具体语言的侧重点上。面向对象编程语言对函数的可访问性有附加要求,需要在声明函数时告知该函数的可访问性,目的是兑现面向对象概念中的封装特性。函数式编程语言更喜欢使用关键字来彰显语言把函数和变量看作同等地位,允许函数向变量一样被自由传递。
APL的函数需要明确被定义的位置,函数被定义在哪里?是在源代码中随意定义,还是定义在具体类型中,这关系到开发者使用语言时如何组织逻辑。如何定义函数是不同编程语言表现各自特色的场景,但最关键在于开发者如何更好的定义函数,比如:职责单一、命名考究和参数不过三个等。
B2 运算支持 APL需要支持简单的代数运算和布尔运算。简单的代数运算一般包括:加、减、乘和除,布尔运算一般包括:与、或和非以及异或。代数运算是计算的基础,布尔运算是行为系统进行流程控制的基础。
虽然这些运算都很基础,对于类似:绝对值、对数或者指数等运算,一般的编程语言不会提供符号来进行支持,而是选择使用数学计算库来进行支持。
B3 顺序、条件与循环 冯诺依曼对程序执行的基本描述,在APL中一般体现在顺序、条件与循环三种结构上,编程语言大部分继承了C系编程语言的风格,通过选择判断来改变走向,通过循环来重复执行,有一些语言会提供类似三目运算符的支持,当然,switch也被归与这种功能。
APL使用顺序、条件与循环,在配合运算支持,就能够实现选择以及遍历循环,可以用来处理简单问题了。
B4 类型、对象以及函数的关系 APL中的函数与对象或类型之间的关系在不同的语言实现中有不同的关系。有些编程语言将函数作为对象的一个属性,虽然不同对象的函数实现是一样的,但是不同的对象具有不同的函数实例。有些编程语言将函数作为类型的属性,一个类型会构造出许多对象,而不同对象所使用的函数实例是同一个。
函数在执行时,APL一般会提供指向函数所关联对象的函数指针,比如:this,这样函数在运行时可以访问对象属性,这就形成了狭义上的闭包特性,也就是函数需要形参以及运行环境(函数中的自由变量)确定下才可以执行,在探讨函数时,也需要关注到函数需要的运行环境。
B5 多态和重载 重载表示函数名相同,但函数的形参不同,可以是个数的区别,也可以是类型的区别。在强类型的语言中,对于重载的支持一般没有多少问题,但是在动态类型语言中,尤其是弱类型语言中,对重载往往是无法支持的,因为形参可以是任何类型。
多态与重载看起来很相似,但实际上没有多少关系,它是面向对象的基本特性之一,父类定义方法,子类进行覆盖,调用时根据对象的具体类型来选择适合的方法,一般面向对象类型的语言,不论是动态还是静态类型语言,都会支持多态,因为这是构建复杂系统的必要元素。
B6 一等公民函数 函数是否作为一等公民需要APL做到以下三点:第一,函数能够赋给变量或者存储在列表等数据结构中;第二,函数能够作为其他函数的形参;第三,函数能够作为其他函数的返回值。这三点说到底就是要求函数能够像普通变量一样,出现在赋值语句中,出现在函数的形参以己返回值中。
一般编程语言为了支持函数作为一等公民,专门独立出一个关键字来声明函数,就像声明变量一样,有些编程语言则依靠自己的类型系统将函数与类型进行挂钩。函数作为一等公民能够让编程语言更加自由的处理数据,能够更细粒度的复用和组织代码。
B7 高阶函数 APL支持函数作为一等公民后,可以选择是否支持高阶函数,高阶函数的特性很多,但主要有两点:第一,可将其他函数作为输形参,在内部调用这些函数以实现逻辑抽象。例如:对集合的操作(如:遍历或筛选)可通过传入不同函数实现复用,避免重复编写控制流程代码;第二,可动态生成并返回一个新函数。这种特性支持闭包(Closure)机制,允许返回的函数保留其创建时的上下文环境,实现状态封装和行为定制。
APL可以选择支持集合的流式处理,通过接收函数形参来控制对集合的访问,同时函数实参可以拥有状态,这样无形中增加了函数式处理的动态性和适应性。
B8 异常机制 APL的异常机制有利于编写健壮的程序,该机制主要包含:异常定义、抛出方式和捕获机制。APL通过内部实现异常表以及类似goto的机制来支持异常机制,但是还需要提供异常定义的抽象,优秀的编程语言能够提供层级式的异常体系,将语言自身和自定义异常统一在一起。抛出方式一般通过关键字来实现,比如:throw,捕获机制一般使用catch关键字来完成。
由于try/catch表述方式来自于理论,所以具体语言在实现异常机制时没有多少发挥空间,好处在于不会存在由于语言设计者水平导致的问题。简单的异常机制只是抛出和捕获,而完善的异常机制是将它与语言融为一体。

       开发者大部分的代码都是在使用APL的行为系统,从B1到B8也是一个不断演进的过程,APL在行为系统上对开发者的约束有限,但是开发者需要长时间的磨练才能够将行为系统用到极致。

       对于APL的行为系统而言,B1到B3是最基础的,它能够帮助开发者拥有面向过程的开发体验,用面向过程的方式来解决问题,B1是必须要使用的,无论如何你都需要定义一个函数,哪怕是main函数,来将自己的逻辑嵌入其中。

       B4和B5是面向对象对于行为的要求,面向对象通过多态的方式来实现系统扩展,同时多态可以降低新代码引入时的影响面,尽可能的支持开闭原则,做到对现有系统最小的影响,这是工程学的要求,是最基础和最重要的。

       在函数式编程越发流行的今天,B6和B7能够以更细粒的的方式复用代码,同时函数的无状态特性使得程序具有更好的并行效率,函数式编程与面向对象不矛盾,从某些层面上来看是更好的补充,能够将变化从对象或类型上剥离,提供更好的抽象形式。

       开发者对于异常机制褒贬不一,但是现代工业化程序都需要有B8的支持,重点还是在于异常定义的设计,在于是否会用,更在于能否用好。

一个例子:APL <|— JavaScript

       JavaScript并非出身象牙塔的语言,未经历学术界长时间的审核,这点和Java有很大的不同。JavaScript一般运行在浏览器中,浏览器面对的资源种类要多于JVM,而且要求JavaScript能够尽可能运行而不崩溃,一定程度上浏览器的实现要在韧性上优于JVM。以firefox为例,其代码规模在2000万行,而同期Linux Kernel的规模在4500万行,作为一款应用,其规模之大可见一斑,(浏览器)不愧是第一应用。

       JavaScript类似Java,在大多数实现中,都拥有即时编译的能力,拥有不错的运行时性能。不同的浏览器实现遵循一致的标准,即ECMAScript,该标准按照年度进行发布。接下来使用APL中的类型、运行和行为系统分析一下JavaScript。

JavaScript类型系统

       JavaScript采用基于原型链的类型系统,相比较Java等强类型系统,类型概念薄弱,没有从一开始就规划好,而是随着时间和使用量的上升修修补补而来的。其类型系统特点如下:

编号 名称 描述
T1 定义变量 变量定义语句:
var winners = 2;
其中var是关键字、winners是变量、=是赋值以及2是字面值(表达式)。遵循的形式包括以下元素:关键字、变量、赋值与表达式。
T2 内建的类型 有缺省类型undefined,变量未初始化时都是该类型,Java没有这个,否则编译无法通过。
typeof用来获取类型的字符串表示,相比之下Java的getClass()考究一些。
null表示对象不存在。
NaN,不是数字,not a number,数字计算边界类型,在Java中往往会通过异常抛出来,而不用徒增一个类型。
相等判断,JavaScript比较复杂,==这种比较有类型转换的支持,会将两边的值先往数字上靠,然后再比较,也就是说:"21" == 21 为真。如果需要联合类型一起比较,那就需要使用===,也就是说:"21" === 21 为假,大部分编程语言的==相当于JavaScript中的===,这完全属于JavaScript设计者水平问题导致的问题。
不同类型的数据运算,Java只支持“+”,对于“-”、“”和“/”没有字符串与数值的运算,JavaScript对四则运算的支持比较随意,比如:`"21" "3"` 等于数值类型的63。
T3 字符串的定义以及功能 string是JavaScript中的原生类型,在有方法调用时,会转为对象String,注意S的大小写,小写为原生类型。字符串在Java中是String类型的实例,而JavaScript中字符串是原生类型,JavaScript中chatAt会返回起始位0向对应下标的字符,如果越界,会返回空字符串,而不是undefined。
但是JavaScript对于数组的越界访问就是返回undefined,可以看出JavaScript语言自身概念不统一,数组和字符串却不一样。
T4 自定义类型 JavaScript定义对象的方式是使用"{}"包裹键值对数据的定义,类似:
var student = { name: "weipeng",gender: 1}
这个对象说到底就是一堆属性的集合。Java创建对象需要使用new,而JavaScript就用花括号。属性声明时没有访问修饰符,在封装性上是弱于传统面向对象编程语言,或者说JavaScript天生不具备构建超大规模系统的潜力,如果硬上,伤人害己。
this关键字与Java类似,Java会在对象中找属性而不用显示的使用this。
null表示对象不存在,与Java类似,undefined表示值未初始化。
JavaScript使用构造函数来创建自定义类型的对象实例,例如:
function Student(name, gender) { this.name = name; this.gender = gender } var student = new Student("weipeng", 1);
T5 类型与对象的关系 JavaScript可以使用instanceof来判断对象所属的类型(或者说构造函数),比如:
if (student instanceof Student) {}
因为JavaScript使用function来定义类型,typeof任意对象,返回的都是object这个字符串,而使用instanceof则可以返回是哪个构造函数创建的该对象。
JavaScript对象中有一个constructor属性,该属性指向创建该对象的函数。JavaScript使用new与构造函数来创建对象,当对象创建时,会使用constructor属性记录创建它的构造函数。
Java中也有instanceof关键字,也可以从对象角度出发,通过getClass()得到类型后再判断,Java显得更加规范和考究一些。
JavaScript的对象是利用副本来复制出对象实例,而不是基于类,构造函数拥有一个prototype的属性,prototype就是一个对象,用它来复制新对象。例如:Student.prototype可以访问该构造函数的原型。
在继承关系上,JavaScript子构造函数的原型使用父对象,采用原型链式结构实现单一继承,同时支持super在构造函数中的使用。
T6 对象的生命周期 JavaScript有垃圾回收器,所以在使用体感上类似Java,只用关心变量和对象的声明,背后内存空间的申请和释放由虚拟机负责。JavaScript没有类似Java的动态代码块,只是简单的对象创建、赋值以及销毁,也没有类型的静态代码块,在语言层面的生命周期上显得比较单薄。
T7 抽象数据类型的支持 JavaScript数组是对象类型,使用[]字面量方式声明,比如:var array = [1, 2, 3, 4, 5],或者new Array(3)形式创建,以下标的形式访问数组元素。JavaScript中的数组有排序功能。
与之相对的Java有些不同,Java中的数组是不可变的,而JavaScript中的数组可变,允许修改数组,通过使用push方法,可以向数组中增加元素,不可变的目的是保证数组元素在内存中是连续的,而JavaScript不见得。
使用for循环可以遍历数组,例如:for (var i = 0; i < array.length; i++) { console.log(array[i]); }
或者
for (var item in array) { console.log(item);}

       JavaScript的类型系统比较实际,没有类型概念,能解决问题,相比其他传统面向对象编程语言而言比较粗糙。

JavaScript运行系统

       JavaScript一般运行在浏览器中,有垃圾收集器的支持,浏览器能够提供给JavaScript对于线程和网络的支持。其运行系统特点如下:

编号 名称 描述
R1 虚拟机与运行环境 需要运行在虚拟机上,node.js或者浏览器。
R2 事件机制 JavaScript有面向界面的元素控件,控件用于响应键盘、鼠标等输入设备的事件。Java有AWT/Swing,也支持设备的输入,但是种类少于JavaScript,或者说浏览器的硬件驱动在广泛度上远高于JVM。
JavaScript的事件定义和事件处理机制是浏览器定义和提供的,比如:button.onclick = function (event) {};
事件抽象是统一的,事件处理器对于事件进行响应,可以处理逻辑,或者调用其他控件来向用户进行展示。
R3 定时器 JavaScript提供了超时执行setTimeout和定时执行setInterval,这些方法是window对象所拥有的,也可以说是VM提供的。
通过传递一个函数以及时间,就可以进行执行,以setTimeout为例:setTimeout(() => { console.log("invoke"); }, 2000);
表示2秒后,执行传入的函数。
Java中有Timer和ScheduleExecutorService,功能上更丰富一些。
R4 输入和输出 浏览器端的JavaScript没有直接访问操作系统标准输入输出流的能力。Node.js通过 process对象提供了对标准输入 (stdin) 和标准输出 (stdout) 流的抽象。在浏览器环境中运行的JavaScript本身不具备独立的输入输出(I/O)能力,其核心规范(ECMAScript)并未定义原生的I/O操作。
浏览器环境下的输入输出功能完全依赖于宿主环境(浏览器)提供的特定API实现,比如:使用DOM事件,利用html元素获取用户输入。
R5 网络编程 JavaScript规范,也就是ECMAScript规范未定义任何网络 I/O 接口,不具备原生 TCP/UDP 操作能力,只能通过使用应用层协议进行外部访问,比如:fetch或者XMLHttpRequest等。
Node.js能够提供完整网络模块,通过net模块支持TCP,dgram模块支持UDP,可创建Socket服务器或者客户端。
R6 多线程支持 JavaScript一般是单线程的,在HTML5规范中通过引入了工作线程来增强其响应性,使其获得了多线程处理能力,但是相比Java,JavaScript在多线程功能上有诸多限制。使用工作线程的方式:var worker = new Worker("xxx.js");
其中xxx.js是一个JavaScript文件,可以在本地或者网络上,通过使用worker.postMessage("msg")向工作线程发送消息,而工作线程,也就是运行xxx.js的线程通过设置onmessage处理函数来进行消息处理,例如:onmessage = (msg) => {}
工作线程不能访问DOM,发送一个对象时,工作线程会得到对象的副本,而不是Java中对象的引用。
与Java相比,二者都有工作队列,JavaScript的线程惰性更强一些,Java主动一些,且一定程度上隐藏了工作队列。

       JavaScript一般运行在浏览器中,因此在运行系统上显得比较薄弱,但是node.js的出现补足了JavaScript在这方面的缺陷,只不过这种能力并非ECMAScript所定义的,不是标准。

JavaScript行为系统

       JavaScript一般运行在浏览器中,有垃圾收集器的支持,浏览器能够提供给JavaScript对于线程和网络的支持。其运行系统特点如下:

编号 名称 描述
B1 定义函数 JavaScript定义方式为关键字、名称和形参组成,例如:function add(x, y) {Return x + y;}
function是JavaScript中的关键字,因此函数也是JavaScript中的基本类型,其形参与实参的理念与C系一致。
浏览器会首先分析所有function的声明,然后再执行JavaScript代码,JavaScript除了声明函数,还可以采用函数表达式和箭头函数(或lambda表达式)。
函数表达式,例如:const pf = function(x) { console.log(x);}
调用时只需要pf()即可,当然上述表达式可以使用箭头函数简化为以下形式:
const pf = (x) => console.log(x);箭头函数和函数表达式都属于匿名函数,而声明函数可以考虑是匿名函数加一个赋值。
B2 运算支持 JavaScript支持布尔运算和代数运算。
B3 顺序、条件与循环 JavaScript支持C系风格的条件与循环。while(x > 0) {}或者for (var x = 0; x < 10; x++) {}或者if (x > 0) {}
B4 类型、对象以及函数的关系 JavaScript函数调用在实参到形参转换时使用值传递,与Java类似,现有语言基本都是值(或者地址)传递。函数也是一种对象,可以被赋值操作,函数也可以是对象的属性,此时被称为方法,比如:var student = { name: "weipeng", say: function(content) { console.log(this.name, content); } }
调用时需要以对象作为前缀,比如:student.say("xxx");,输出:weipeng xxx
这里实际上是一个函数表达式,赋值给了student对象的属性say。Java则需要在类型中定义方法,约束要强于JavaScript,但自由度上逊于JavaScript。
B5 多态和重载 JavaScript是弱类型语言,不支持方法重载,但可以通过接收函数参数作为变通实现重载的方式。
JavaScript的继承体系采用了原型链的方式,子对象能够覆盖父对象的方法,当从对象数组中依次调用方法时,会正确委派到合适的方法上,这表示JavaScript对多态的支持是符合要求的。
B6 一等公民函数 JavaScript中的函数与值一样,是一等公民。对象的属性可以是函数,可以通过赋值加以更换。Java中方法也有一等值的地位,但是没有JavaScript这么彻底,毕竟还有类型摆在那里,JavaScript中对象的方法都可以随意更换。
B7 高阶函数 JavaScript有匿名函数也支持闭包,Java使用匿名类来实现匿名函数,在Java8后,使用lambda会好一些。
B8 异常机制 JavaScript支持try/catch,有Error对象的定义。没有类似Java在方法上的throws声明,以及checked/unchecked异常体系设计,工业化设计显得比较弱。

       JavaScript的行为系统设计灵活,一等公民函数使得JavaScript在编码中显得比较自由,函数传递在JavaScript中运用的非常广。

results matching ""

    No results matching ""