44. JavaScript 函数
引言
函数是几乎所有你用 JavaScript 编写的有用的功能的核心。一般而言,函数可以将一个程序分为若干逻辑块,每个块实现某个特定的功能。函数是 JavaScript 程序设计语言最主要的特征,而 JavaScript 之所以具有这么大的吸引力原因之一就是它特有的使用和创建函数的方式。如果你之前曾经用 PHP 或 Java 之类的语言编写过某些程序,那么你就会对 JavaScript 中的函数感觉得心应手;如果你没有这样的经验,也不用担心。函数很重要,但它们也并不会难到把你搞糊涂。本文会阐述为什么应该了解函数,然后我们将介绍函数的语法,并说明如何创建和使用函数。
注意,点击此处可以下载本文函数示例,在本文接下来的部分中的适当地方也有链到这些示例的链接。
本文结构如下:
什么是函数以及为什么要使用函数
你肯定不希望每次要执行一项特定运算的时候都得去拿你的规范说明来唤起自己的记忆;最好的做法是直接将该运算的步骤一次性编写好,然后将这些步骤打包成一个 calculateSomething
函数,下次要执行同样的操作时只需要指向该函数。这种打包一系列命令的简单做法意味着你可以专注于自己的代码所实现的操作,而不是这些操作内部的具体细节。你可以将自己编写的函数看作是位于 JavaScript 内置核心之上的一个层;你是在自己的特定应用程序的环境中创建表达性和可理解性更强的新指令。
有了这个理念,“为什么?”要使用函数的的问题就有了一个非常简单的答案:函数是基本的组成模块,通过函数你就可以结构化自己的代码的,从而提高对其作用的理解,并可以重复使用你所编写的函数,以避免在多处地方重复编写同样的代码片段。如果你将自己的程序分为若干小片段,每个小片段完成一项确定的任务的话,该程序就更容易编写了。
此外,在深思熟虑之后,将你的代码分解为若干函数,会使得将来对代码的维护容易得多。比如说,我们假设明年的夏令时间会发生改变。如果你的整个项目中共有八十五次夏令时计算,你对代码的每一次更新的都可能会产生新的漏洞;而且这样做是重复的,手动的,而且很容易导致bug。另一方面,如果是对单独的 calculateDaylightSavings
函数进行改动的话,你只需通过一次修改就可以将这种改动级联到程序的其它部分,这与CSS中样式在整个页面上的级联非常相似。这样,函数使得程序维护更不易出错,而且实现起来也更容易成功。
函数的语法
定义你自己的函数是非常简单的。作为范例,我们在一个页面上创建一个能随机改变某个元素的背景颜色的函数:
function setElementBackground() {
var red = Math.floor(Math.random() * 256);
var green = Math.floor(Math.random() * 256);
var blue = Math.floor(Math.random() * 256);
var obj = document.getElementById('element_to_change');
if ( obj ) {
obj.style.background = 'rgb(' + red + ',' + green + ',' + blue + ')';
}
}
不必太担心这个函数内的代码,此时此刻我希望你关注的事这个函数的语法,它有四个重要特征:
- 函数的声明常常由关键字
function
开头,这样就能直观地说明这是一个函数。 - 第二点是函数名,在本例中是
setElementBackground
(我一般用驼峰式大小写来做函数名)。函数名是很重要的,这是因为为了使用和重用这段代码,你必须记住函数名。我们应该确保函数名精确地描述了该函数的作用;我想你一定会同意比起coloursAreNice
或crazySetter
来,setElementBackground
作为函数名要好得多,描述性也要更好一些。 - 紧接在函数名之后的是一对圆括号。在圆括号之中是函数的参数列表,通过该列表你就可以提高自己的函数的通用性,从而提高其可复用性——你可以更容易地将它们用到更多的情况之中。参数一个很有用的概念,但是可选的,因此我将在下一部分来详细讨论这个问题。
- 最后是一对大括号,其中包含着一些代码:大括号代表着一个 JavaScript 代码块。这个代码块之中的一切都会在函数被调用时按次序执行,就像你以前写过的其它 JavaScript 代码片段一样。
函数的使用
我们的函数定义完成后,在代码中其它地方调用这个函数只需要写:
setElementBackground();
这样就可以了!你不再需要关心 setElementBackground
的复杂的内部细节;你已经写好了它的代码,所以现在你就可以轻松地在自己想要的地方调用这个函数,并享受代码重复使用的好处。
可以看到,我们刚才写的那个函数是完全独立的。这个函数执行一些操作,然后就退出了;它既不需要从调用自己的代码那里获得输入,也不会向调用自己的程序返回任何信息,告诉它发生了什么。当然, JavaScript 允许我们编写比这个函数更灵活的更富于信息交换的代码,下面我们就来看看该如何向函数输入的信息和从函数输出信息。
参数
向函数传递信息以影响其行为,在很多情况下这都能使函数更灵活,更有用。比如说,被 setElementBackground
函数改变背景颜色的元素的id
是通过硬编码的方式确定的;如果我无论何时调用该函数,都能指定改变页面上某个元素的背景,那应该会很不错,因为这样我就可以将这个函数重复用于不同的元素,而不是把整段代码都复制下来了。通过参数我们就可以做到这一点。
在前面我们曾提到过,函数定义紧接在函数名之后的是一对圆括号。这就是该函数的参数列表。为了接受来自调用程序的输入信息,你只需指定一个用逗号分隔的参数列表,这些参数就是你的程序要接收的信息的对应变量。你可以指定任意多数量的变量,并且参数列表中所提到的所有变量名都可以在函数体之内被引用,就跟任何其它变量一样。修改后的 setElementBackground
函数如下所示(点击查看其效果):
function setElementBackground( elementID ) {
var red = Math.floor(Math.random() * 256);
var green = Math.floor(Math.random() * 256);
var blue = Math.floor(Math.random() * 256);
var obj = document.getElementById( elementID );
if ( obj ) {
obj.style.background = 'rgb(' + red + ',' + green + ',' + blue + ')';
}
}
通过参数传入元素 ID 来调用这个函数是很简单的:
setElementBackground( 'element_to_change' );
如果你调用了该函数,却忘记传给它一个参数值,那么该参数就会取 undefined
值。你可以在自己的函数体内添加参数检验代码以防止无心之错:
if ( elementID == undefined) {
// 如果调用程序没有提供`elementID`变量,
// 这个表达式的值就为‘true’
// 你就可以在这个if语句中编写一些代码
// 来阻止程序出错。
}
在函数参数方面,有一个容易搞糊涂但又有好处的特点,就是参数列表中的变量名与传入函数的变量名无关。如果 elementID
被定义为函数的自变量, JavaScript 就会在函数内部创建一个名为 elementID
的变量,且这个变量不会影响任何函数外部的变量——在这个函数的外部你还可以再写一个函数,用同样的变量名,但这个变量的值不会随着第一个函数内部的任何语句而改变。比如:
var elementID = "No change!";
setElementBackground( 'element_to_change' );
alert( elementID ); // Alerts "No change!";
这种做法有个非常严重的副作用。 JavaScript 在该函数内部创建了一个新变量,这就意味着它对自己的内部变量所做的任何改变都不会影响到传入的变量。我将在对象和 JavaScript 最佳实践中详细讨论此概念(称做作用域)。现在我们来看一个简单的例子。我定义了一个 substring
函数,该函数会接收一个字符串和一个起始点:
function substring( obj, start ) {
obj = obj.substring(8);
}
var myString = "This is a string!";
substring(myString, 8);
alert(myString); // Alerts "This is a string!"
即使我们在函数内部对 obj
变量进行了重新赋值,将其值设为内嵌的 substring
方法的结果,myString
也不会受到一点影响;只有位于 substring
内部的 myString
的副本值被改变。外部变量完全不知道函数内部发生了什么。
这就造成了函数内外的通信问题:如果改变参数的值不会对函数外部造成任何影响,你该如何将来自函数的信息传回其调用程序呢?下面我们就来谈谈这个问题。
返回值
用函数来进行运算,并将运算结果传回位于其它地方的调用程序,这是很常见的做法。举例来说,让我们的 setElementBackground
函数返回一个颜色值的数组,以便在其它地方使用,这种功能可能会很有用处。只需要使用 JavaScript 提供的 return
关键字就可以轻易地做到这一点,如下面所示:
function setElementBackground( elementID ) {
var red = Math.floor(Math.random() * 256);
var green = Math.floor(Math.random() * 256);
var blue = Math.floor(Math.random() * 256);
var obj = document.getElementById( elementID );
if ( obj ) {
obj.style.background = 'rgb(' + red + ',' + green + ',' + blue + ')';
}
return [ red, green, blue ];
}
此处添加的这条简单代码意味着,你现在可以调用此函数,并通过变量保存该函数的返回结果:
var my_result = setElementBackground('element_to_change');
即使你的函数不需要返回任何值,或者是没有可以返回的实际值,分别返回 true
或 false
以提示程序运行成功或失败也是很好的习惯。下面,我们来对 setElementBackground
函数进行修改,如果应该传入的 elementID
不存在的话,该函数就会返回 false
:
function setElementBackground( elementID ) {
var red = Math.floor(Math.random() * 256);
var green = Math.floor(Math.random() * 256);
var blue = Math.floor(Math.random() * 256);
var obj = document.getElementById( elementID );
if ( obj ) {
obj.style.background = 'rgb(' + red + ',' + green + ',' + blue + ')';
return [ red, green, blue ];
} else {
return false;
}
}
你可以通过检测其返回值来查看该函数的执行是否正确,如:
if ( !setElementBackground('element_does_not_exist') ) {
alert("Something went wrong! `element_does_not_exist` doesn't exist!");
}
此外,请注意一下在调用 return
关键字时,该关键字实际上是结束了你的函数的执行,并将执行权利返回到调用函数的地方。在 return
后面的代码不会得到执行——它会被直接忽略掉。
总结
读完本文,现在你差不多已经学到了所需要知道的一切,这样你就可以在自己的程序中开始大肆使用函数了。这些知识是编写高质量 JavaScript 代码的基础知识,如果你经常将自己的代码打包进命名合理的函数,以利于重复使用的话,你的程序就会变得更有条理,更清晰,更易读,也更易于理解了。
练习题
- 什么是函数?为什么说函数很有用?
- 如何定义函数?
- 怎样向函数传递信息?为什么要这么做?相反地,怎样从函数获取信息?
- 如果我们将一个颜色数组传给`setElementBackground`岂不更好?试试看修改一下代码以接收另一个参数,并在函数内部的使用该变量来替代随机背景颜色。