我看JavaScript(三)——语言或程序的动态性

前一文中我们初步探讨了社区对「语言动态性」的一种通识:

动态语言将静态语言在「编译时的常见任务」,推迟到运行时,通过动态增加新代码、扩展对象及其定义,或修改类型系统来扩展程序本身。

就这种粗浅认识,提出几个关键问题:

  • 第一,编译时的什么任务推迟到了运行时?
  • 第二,扩展程序是什么意思,有什么意义呢?具体又是怎么做到的呢?

发现认识混沌的原因之一——程序理论的不成熟,并且初步给出一种或许也不成熟的程序结构理论。本文在此不成熟的程序理论上,对语言动态性作一次深入的解释的尝试。

编译任务指什么?

按静态语言(例如C)的生产模式,程序的编译与运行是严格分开的,编译一次运行无数次,运行过程中产生的中间数据不属于编译任务。用一个简单的理解,编译产生的是代码区的数据,一次载入并且是只读的,运行时活动在自由存储区(堆或堆栈),此区域任意使用。我们特别注意「运行时动态增加新代码」的“新代码”,程序的形式主要在代码区,「编译任务主要是指构建程序运行时的代码区」,对程序的“运行时编译”(扩展程序的形式)在于在自由存储区增加新的代码。

运行时编译?

然而,编译完成后程序员并不在用户现场,动态的运行时编译难道由用户来完成么?

用户当不需要也不能「直接」增加代码,改变程序的形态,但可间接,间接借程序员之手在自由存储区创建临时代码区。这就是动态语言的关键点,当程序编译完成,交付到用户后,程序的结构动态改变是预先编译的,由用户触发的。例如用户很难(不是不能)改变类定义,但是很容易创建新对象,对象实例本身是由代码组成的。

扩展程序

接下来的一个问题是,程序的形式或形态可以怎么扩展?随意增加代码区么?在前一文中我们总结了程序的各种复合构件,由复合度低到高分别是:语句、块子句、函数、类对象和模块。一支较复杂的程序,它的结构一般是这样的,程序由一个以上模块组成,模块由一个以上类组成,类由一个以上的函数……如此类推就像一棵倒立的树,树上任何节点上的扩展都可以视为对程序形式的扩展。

然而,程序扩展的单位可能要满足一些条件。例如,一条单独的语句能对程序进行扩展吗?程序扩展的单元似乎要满足接入和拆卸的灵活性,故扩展单元执行的独立性和封装性必得到保证,因此,语句、块子句和函数都不是适合的扩展单元,最合适是类对象。函数是比较独立的,也具备封装特性,但是抽象度不够,多于一个函数的计算任务扩展不了了。

JS动态性与JS程序扩展

就目前我的经验,JS程序扩展有以下几种:

  • 第一,使用固定的类象(定义)创建/构建新对象实例;这个只需运行时的一些数据,闭包是最常的情景;
  • 第二,删除类对象的属性;
  • 第三,函数传递,更改函数的this,就是为其它类新增方法;例如为DOM对象绑定事件回调;
  • 第四,更改父类或原型;

扩展程序的意义

【程序】,与程序要完成的【计算任务】是一体两面。语言或者程序的动态性是源于计算任务的动态改变。所以,如果程序(面对的计算任务)比较的简单的,那就没有扩展程序的必要。但是,如果程序面对的计算任务比较复杂多变,例如JS的事件编程会有多个执行点,并且很多计算数据要到运行时才能确定,才需要扩展程序形式。举例如运行时加载插件,使用对象作缓冲区等。

如何扩展程序

有了对程序形式扩展基本认识,JS动态性主要通过以下一些API实现的:

  • new
  • delete
  • Object.create()
  • Object.defineProperty()
  • Object.setPrototypeOf()
  • Function.call/apply
裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.