JavaScript Allongé 第一呷 :基础函数 (2)

啊!我想要有一个参数

到现在为止,我们已经了解了没有参数的函数。只说我们的函数没有任何参数,甚至还没说参数是什么。
大多数程序员非常熟悉参数,中学数学就讨论这个了。所以你知道他们是什么,而我也知道你知道他们是什么,但是请耐心对待这个解释!
让我们写一个带参数的函数:

function (room) {}

这个函数有一个参数room,没有函数体。这里是一个带有两个参数但没有函数体的函数:

function (room, board) {}

我很确定,你非常适应这个函数有两个参数的想法,room和board。参数用来做什么呢?当然是用在函数体中。你觉得这是什么?

function (diameter) { return diameter * 3.14159265 }

这是一个给出直径来计算圆周长的函数。我这样解读"当被应用到一个表示直径的值,此函数返回直径乘以3.14159265"。
记得使用一个无参数的函数时,我们写 (function () {})()。使用带参数的函数时,我们把参数放在小括号中,像这样:

(function (diameter) { return diameter * 3.14159265 })(2)
//=> 6.2831853

你也不会对看到如何编写和使用带两个参数的函数而感到惊讶:

(function (room, board) { return room + board })(800, 150)
//=> 950

函数与函数体快速总结

从例子看,在函数体的表达式中如何使用参数对你而言大概是非常明显的,尤其是如果你已经使用过任何编程语言(除了BASIC方言,我记得是在中学,当你调用程序时不允许参数)。
表达式由任意的值(比如 3.14159265, true, 和 undefined),连接表达式的操作符(比如 3 + 2),一些特殊的形式如[1,2,3]用于从表达式中创建数组,或者function (参数) {体语句}用于创建函数组成。
可能其中一个重要的语句是return 语句。return 语句接收任何有效的javascript表达式。

这种松散的定义是递归的,所以我们可以直觉地这样理解(或者使用其他语言的表达式),既然函数可以包含带表达式的返回语句,我们可以写返回函数的函数,或者包含另一个数组表达式的数组,或者返回数组的函数,函数数组,返回函数数组的函数,等等。

function () {
return function () {}
}

function () {
return [ 1, 2, 3]
}

[1, [2, 3], 4]

function () {
return [
(function () { return 1}),
(function () { return 2}),
(function () { return 3})
]
}

按值调用


就像大多数的现代编程语言一样,javascript使用“按值调用”求值策略。这意味着当你写了一些代码将函数应用到表达式时,javascript对所有这些表达式求值并将所得到的值应用于函数。
所以当你写:

(function (diameter) { return diameter * 3.14159265 })(1 + 1)
//=> 6.2831853

内部发生的是,表达式 1 + 1 首先被计算,产生2,然后我们的周长函数被应用于2。

变量和绑定
现在,一切看起来简单明了,我们可以继续前进更详细地讨论参数。我们打算从function (diameter) { return diameter * 3.14159265 }努力深入到像这样的函数:

function (x) { return (function (y) { return x }) }

function (x) { return (function (y) { return x }) }只是看起来疯狂而已,就像我们正在学习英文作为第二语言,而老师向我们保证我们很快就能使用类似antidisestablishmentarianism这样的单词,除了希望使用长单词听起来让人印象深刻以外,这看起来不再有吸引力,直到我们发现自己想要讨论在19世纪英国政治中英国教会中的角色。
但是这里有另外一个学习antidisestablishmentarianism这个单词的理由:我们也许会学习前缀和后缀在英文语法中如何工作。这和function (x) { return (function (y) { return x }) }是一样的,它本身具有一定的重要意义,它也是一个学习造函数的函数,环境,变量,等等的优秀借口。

为了讨论它是如何工作的,我们需要同意几个术语(你可能已经知道它们了,但我们还是一起检测一下然后“同步我们的词典”)。第一个x,是在function (x) ...中的x,它是一个参数,在function (y) ...中的y是另一个参数,第二个x,是在{ return x }中的x,它不是一个参数,它是一个指向值的表达式。参数和变量以同种方式工作,不管我们是在讨论 function (x) { return (function (y) { return x }) }还是仅仅讨论简单的function (x) { return x }。

每次,一个函数被调用(调用意味着被应用到零个或更多参数),一个新环境就被创建了。一个环境是一个(可能是空的)字典,根据名称将变量映射到值,在表达式中的x我们称之为一个变量,它自身是一个表达式,通过查找环境中的值来被计算。

值是怎样被放到环境中的呢?对参数而言,这很简单。当你将函数应用到参数时,就为字典中的每一个参数放了一个入口,所以当我们写:

(function (x) { return x })(2)
//=> 2

发生了这些事:
1 javascript将这个整体解析为一个由几个子表达式组成的表达式。
2 之后它开始对表达式求值,包括对子表达式求值。
3 一个子表达式,function (x) { return x },计算结果为一个函数。
4 另一个2,计算结果为数值2。
5 javascript现在计算将函数应用到参数2。下面就是它有趣的地方...
6 创建一个环境
7 值2在环境中被名称x所绑定。
8 表达式x(函数的右侧)在我们刚刚创建的环境中被计算。
9 我们在环境中计算的变量的值是在该环境中绑定给变量的名字的值,也就是2。
10 这就是我们的结果

当讨论环境时,我们将使用一个不令人吃惊的语法来显示绑定:{x: 2, ...}。意思是,这个环境是一个字典,而值2被绑定给了名称x,并且在字典中或许有其他的我们现在不讨论的东西。

按共享调用

早些时候,我们区分过javascript的值类型与引用类型。在那时,我们看javascript如何区分对象是否是相同的对象,现在是时候再看一下值和引用类型的区别了。
有一个属性javascript一直在维护:当一个值-任何值-被作为参数传递给函数,这个被绑定在函数环境中的值必须与原始的是相同的。
我们说过javascript把值绑定到名称,但未说过把值绑定到名称意味着什么。现在我们可以详细点了:当javascript把一个值类型绑定到名称时,它复制了这个值然后把副本放在环境中。正如你记得的,像字符串和数值的值类型如果有相同的内容它们是彼此相同的,所以javascript可以建很多字符串、数值或者布尔值的副本只要它想。

引用类型呢?javascript不会在任何环境中放引用类型的副本。javascript在环境中放引用类型的引用,当值需要被用到时,javascript根据引用获得原始值。

因为许多引用类型可以共享同样的值,并且因为javascript将引用作为参数传入,javascript可以说是实现了“按共享调用”的语义。按共享调用一般被理解为按值调用的特殊化。并且它解释了为什么一些值被称为值类型而其他值被称为引用类型。

有了这些,我们就准备好看闭包了。当我们将值类型、引用类型、参数和闭包的知识结合在一起的时候,我们就会理解为什么这个函数总是计算结果为true,不管你用了什么参数:

function (value) {
return (function (copy) {
return copy === value
})(value)
}

posted on 2014-04-20 21:14  叫我钱了个浅  阅读(446)  评论(0编辑  收藏  举报

导航