函数与闭包的前世今生(二)

函数与闭包的前世今生(二)
0

本系列的其他文章:
函数与闭包的前世今生(一)
函数与闭包的前世今生(三)
函数与闭包的前世今生(四)

2、重新审视函数

函数与闭包的前世今生(一)中,我提到闭包有两种定义,一种认为闭包是函数,另一种认为闭包是函数与自由变量的集合。我个人比较认同后一种定义。

我们经常谈论函数闭包,但是却几乎没思考过函数是什么(函数的定义)。对于只学习过 JavaScript 而没有学习过 C 语言的人来说,他们可能不理解函数与闭包的区别,因为在一个变量的作用域内定义的函数都能访问该变量,函数能访问其定义以外的变量看起来是自然而然的事情。很多人也不知道底层的 JavaScript 解释器是如何实现闭包的,不知道实现闭包其实比没有闭包、只有全局变量、函数参数和局部变量要复杂一些。

首先我们来看看编程语言中的函数是如何应运而生的。

2.1、图灵机与“古代”的编程

伟大的数学家、密码学家阿兰·图灵提出了图灵机这种计算机模型。图灵机的原始版本是:一台机器有一个读写头和可以存储有限状态的内部存储器,读写头可以移动和读写一条纸带,纸带上只能存储一个一个的二进制位,机器根据读到的纸带上的信息以及内部状态来决定如何移动或者写纸带。机器内用于存储状态的内部存储器通常叫做寄存器(register)

image

不久人们就按照这个模型制造了真正的计算机。是的,最早的计算机读写的不是磁盘或者SSD,而是纸带! 但是这个时候计算机能做的计算的种类和过程是固定的,例如只能从纸带上读入两个数然后在纸带上输出两个数的乘积,而不能动态地改变计算机执行的指令(编程)。也就是说这时候纸带的输入数据仅仅是被看作要处理的数据而不是可执行的代码

2.1.1、通用图灵机与可编程计算机

通用图灵机是指可以实现任意一个图灵机的功能的图灵机。有了通用图灵机,我们就可以用一台计算机来实现多种多样的计算功能。怎样实现一台通用图灵机呢?让计算机可编程就行了。可编程计算机不仅可将纸带上的信息看作要处理的数据,还能将其看作代码来执行。

纸带上的可执行代码就是我们常说的机器语言。机器语言的执行单位是指令,也就是说可编程计算机每次从纸带上读取一条指令来执行。一个程序的可执行代码就是由若干条指令构成。

2.1.2、指令集

接下来就产生了一个问题:纸带上的指令应该怎么设计呢?如果设计得太复杂,一来一条指令占用的纸带就会太长,太耗纸带;二来执行指令的计算机也要设计得很复杂,制造成本太高。

一台可编程计算机的指令的设计方式就是这台计算机的指令集

经过计算机科学家和工程师的研究和设计,直到今天各种处理器的指令集相对于高级编程语言(如 C++、Java、JavaScript、Python 等)来说还是很简单的,基本上只有如下几类:

  1. 从输入 / 存储设备读取一个数(通常是 1 / 2 / 4 / 8 个字节的)到某个寄存器中;
  2. 对某个寄存器中的数作一元运算,如按位取反、变换正负号;
  3. 将某个寄存器 a 中的数与另一个寄存器 b 中的数做运算(加减乘除、按位与、按位或等),结果存回寄存器 a;
  4. 将某个寄存器中的数写到输出 / 存储设备中;
  5. 跳转执行另外一个地方的代码;
  6. 根据某个寄存器中的值是否为 0,来决定是否跳转执行另一个地方的代码

大家可以看到,上面没有可以直接实现函数的指令,甚至没有可以直接实现循环结构的指令,只能实现顺序执行以及简单的条件结构。