八、函数

函数是可重用的代码块,只有在被调用时才会执行。它们允许开发人员将他们的脚本分成更小的部分,更容易理解和重用。

定义函数

要创建一个函数,可以使用 function 关键字,后跟一个名称、一组括号和一个代码块。函数的命名惯例与变量相同——使用一个描述性的名称,除了第一个单词以外,每个单词都要大写。

function myFunc()

{

document.write("Hello World");

}

这个函数只是向 web 文档显示一个文本字符串。函数代码块可以包含任何 JavaScript 代码,包括其他函数定义。

调用函数

一旦定义了一个函数,就可以在文档的任何地方调用它,只需键入它的名字,后面加上一组括号。函数名区分大小写,所以字母的大小写需要一致。

myFunc(); // "Hello World"

即使函数定义稍后出现在脚本中,也可以调用函数。这是因为 JavaScript 中的声明是在代码执行之前处理的。

foo(); // ok

function foo() {}

函数参数

函数名后面的括号用于向函数传递参数。为此,必须首先将相应的参数添加到函数的参数列表中。这些参数可以作为常规变量在函数中使用。

function sum(a, b) {

var sum = a + b;

console.log(sum);

}

一个函数可以被定义为接受任意数量的参数。调用该函数时,参数以逗号分隔列表的形式提供。在此示例中,该函数接受两个数值参数,并显示它们的总和。

sum(2, 3); // "5"

像变量一样,函数参数也没有在声明中指定类型。因此,不会自动执行类型检查,函数参数也不限于任何特定的数据类型。

可变参数列表

允许调用一个函数,其参数个数与定义的参数个数不同。如果调用函数时使用的参数较少,那么剩余的参数将被设置为未定义。

function say(message) {

console.log(message);

}

say(); // "undefined"

当调用一个函数的参数比它的定义中的多时,多余的参数将没有名字。可以通过类似数组的 arguments 对象引用这些额外的参数,该对象包含传递给函数的所有参数。

function say() {

console.log(arguments[0]);

}

say("Hello"); // "Hello"

arguments 对象可用于创建 varadic 函数,这些函数能够处理不同数量的参数。举个例子,可以将任意数量的参数传递给下面的函数。该函数遍历参数,并将它们组合成一个字符串,输出到控制台。

function combine() {

var result = "";

for (var i = 0; i < arguments.length; i++) {

result += arguments[i];

}

console.log(result);

}

combine(1, 2, 3); // "123";

返回语句

Return 是一个跳转语句,它使函数退出,并将指定的值返回到调用该函数的地方。举例来说,下面的函数返回其两个参数的和。这个函数又可以作为参数传递给另一个函数,在那里它将计算出结果数。

function getSum(a, b) {

return a + b; // exit function and return value

}

console.log( getSum(1, 2) ); // "3"

return 语句也可以用来在到达 end 块之前退出函数,而不返回任何特定的值。没有返回值的函数将隐式返回 undefined。

function foo() {

return; // exit function

}

console.log( foo() ); // "undefined"

就像变量和参数一样,返回值不进行类型检查。函数需要被适当地文档化,以便函数的用户知道他们的输入和输出应该是什么。

参数传递

参数通过值传递给函数。对于基本类型,这意味着只有值的副本被传递给函数。因此,以任何方式更改参数都不会影响原始变量。

function set(y) {

y = 1;

}

var x = 0;

set(x); // copy of value passed

console.log(x); // "0"

当对象类型用作参数时,传递的是对该对象的引用。这允许函数对原始对象的属性进行更改。

function addFruit(basket) {

basket[0] = "Apple";

}

var fruits = [];

addFruit(fruits); // copy of reference passed

console.log( fruits[0] ); // "Apple"

将新对象赋给参数不会影响函数外部的原始对象。这是因为赋值改变了参数的值,而不是对象的一个属性值。

function makeFruit(basket) {

basket = [ "Apple" ];

}

var fruits = [];

makeFruit(fruits);

console.log( fruits[0] ); // "undefined"

函数表达式

JavaScript 中的函数是对象,特别是函数对象。因此,它们可以被赋给变量并传递给其他函数,就像任何其他对象一样。当一个函数以这种方式使用时,它被称为函数表达式,而不是函数声明。可以使用正常的赋值语法(包括分号)将函数表达式赋给变量。

var say = function foo(message)

{

console.log("Hello " + message);

};

当调用一个函数对象时,引用的是变量名而不是函数名。

say("World"); // "Hello World"

可以在函数内部使用函数名来引用它自己,但是函数名在其他情况下是不必要的。因此,该名称通常被省略。然后,函数表达式被称为匿名函数。

var say = function(message)

{

console.log("Hello " + message);

};

函数表达式通常被用作回调函数,或者被传递给其他函数,或者从其他函数返回。它们允许非常简洁地编写代码,因为函数可以内联,而不必在其他地方定义它。为了说明这一点,下面的例子使用了window.setTimeout函数,它采用另一个函数和一个数字作为参数。该数字指定在调用函数参数之前等待的毫秒数。

// Call anonymous function after one second

window.setTimeout(function() { console.log("Hello") }, 1000);

范围和寿命

变量的作用域指的是可以使用该变量的代码区域。JavaScript 中的变量既可以全局声明,也可以局部声明。全局变量是在任何函数之外声明的,可以从文档中的任何地方访问。另一方面,局部变量是在函数内部声明的,并且只能在该函数内部访问。

var globalVar = 0; // global variable

function foo() {

var localVar = 0; // local variable

}

局部变量的生存期是有限的。全局变量将在脚本运行期间保持分配状态,而局部变量将在其函数执行完毕后被销毁。

console.log(globalVar); // "0"

foo();

console.log(localVar); // throws a ReferenceError

当作用域中的两个变量同名时,就会出现名称冲突。更多的内部作用域具有优先权,因此最里面的作用域具有最高的优先权,而最外面的作用域具有最低的优先权。

var a = "global";

function foo() {

var a = "local"; // overshaddows global variable

console.log(a);

}

foo(); // "local"

console.log(a); // "global"

与许多其他语言不同,JavaScript 中的代码块没有自己的作用域。因此,在控制结构代码块(如循环或条件语句)中定义的变量在代码块结束时不会被销毁。

if(true) {

var x = 10; // global variable

}

console.log(x); // "10"

还有一种创建变量的替代方法,即在不使用 var 关键字的情况下为未声明的变量赋值。这将隐式地将变量声明为全局变量,即使它是在函数中声明的。

function foo() {

a = 5; // global variable

}

foo();

console.log(a); // "5"

以这种方式错误地引入或覆盖全局变量是常见的错误来源。因此,建议始终使用 var 关键字显式声明变量。

var a = 10;

foo(); // replaces value of global variable

console.log(a); // "5"

像函数声明一样,显式变量声明也在脚本执行之前进行处理。因此,变量可以在声明之前在代码中引用。

console.log(a); // "undefined"

var a = "defined";

console.log(a); // "defined"

对于隐式声明的变量来说,这种行为是不同的,因为在为变量赋值的代码运行之前,这些变量是不存在的。

console.log(b); // throws a ReferenceError

b = "defined"; // never executes