08-函数变量
13.6 函数变量
如果你是编程初学者,那你可能需要坐下来再给自己倒一杯咖啡,平复一下情绪:因为接下来要讲的内容是初学者经常纠结的地方,也是需要掌握的重要概念之一。
通常,将数值、字符串、甚至数组当作变量很容易理解,这也导致了习惯性地认为变量就是一些数据(或一个集合,就像数组和对象)。然而,如果认为变量只是数据,就很难意识到函数的所有潜能,因为可以像传递变量那样传递函数。由于函数是动态的,一般我们不会认为函数是一种数据(通常,我们认为数据是静态的)。的确,当函数被调用的时候,它是动态的。不过在调用之前,它与其他变量一样,是静态的。
下面这个比喻可能会帮助理解。假如去超市,你可以将水果看成是数据,比如2个香蕉,1个苹果等。此时决定用水果做冰沙,所以需要买一个搅拌机。搅拌机更像一个函数:它能做一些事情(比如,将水果搅成冰沙)。但当搅拌机在购物车里时,没有通电,此时它仅仅是购物车中的一件商品,可以像对待水果一样对待它:把它从购物车中拿出来放到传送带上、付款、放入购物袋中带回家。只有当插上电,把水果放到搅拌机里,并打开开关的时候,它才变得跟水果不一样。
因此,凡是能够使用变量的地方,都可以使用函数,这意味着什么呢?这意味除了变量的普通用法外,还可以做下面这些事:
- 通过创建一个指向函数的变量来给函数起一个别名。
- 将函数放入数组中(可能混合其他类型的数据)。
- 将函数当做对象的属性(见第9章)。
- 将函数传入到另一个函数中。
- 从一个函数中返回一个函数。
- 从一个把函数当做参数的函数中返回一个函数。
头晕吗?单是写出来,就难以置信的抽象,大家可能会奇怪“为什么地球上会有人做这种事情?”事实上,它的灵活性强大到难以置信,而人们也在频繁地做这些事。
先从上述列表中最容易理解的一项开始:给函数起别名。设想一下:有一个名字非常长的函数,并且想在短短几行代码中多次使用它。全部敲出来会让人筋疲力尽,而且写出来的代码也非常难看。因为函数也是一种数据类型,所以可以创建一个名字更短的变量来代替它。
function addThreeSquareAddFiveTakeSquareRoot(x) {
// 这是一个非常傻瓜式的函数,不是吗?
return Math.sqrt(Math.pow(x+3, 2)+5);
}
// 起别名之前
const answer = (addThreeSquareAddFiveTakeSquareRoot(5) +
addThreeSquareAddFiveTakeSquareRoot(2)) /
addThreeSquareAddFiveTakeSqureRoot(7);
// 起别名之后
const f = addThreeSquareAddFiveTakeSquareRoot;
const answer = (f(5) + f(2)) / f(7);
注意,在“之后”的例子中,作者没有在 addThreeSquareAddFiveTakeSquareRoot 后面使用括号。因为一旦加了括号,就成了函数调用,f就不是 addThreeSquareAddFiveTakeSquareRoot 的别名,而是函数调用的结果。那么,当使用它(比如, f(5) )时就会报错,这是因为f不是一个函数,而只有函数才能被调用。这个例子是故意写成这样的,不过这并不常见。它最开始出现的地方是命名空间,(namespacing),这在Node开发(见第20章)中很常见。例如:
const Money = require('math-money'); // require 是一个用来导入类库的Node函数
const oneDollar = Money.Dollar(1);
// 或者,如果我们不希望在所有调用的地方都使用 “Money.Dollar”:
const Dollar = Money.Dollar;
const twoDollars = Dollar(2);
// 注意oneDollar和twoDollars是同一种类型的实例
在上例中,并没有使用别名 Dollar 去简化 Money.Dollar ,即使这样做也挺合理。
至此已经做了很多锻炼之前的“伸展运动”了,准备活动已经够了,接下来来进行一些更烧脑的抽象思考吧。