本文详解JavaScript中函数的两种定义方式,深入解析参数传递机制、arguments对象的使用限制,以及ES6 rest参数的优雅替代方案。同时提醒开发者注意return语句在自动分号插入机制下的常见陷阱,并提供函数定义练习。
JavaScript函数定义方式
JavaScript中函数的定义方法包括以下几种:
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
以abs()函数为例,其定义结构如下:
- function关键字用于声明函数定义;
- abs为函数标识名;
- (x)括号内声明函数参数,多参数间使用逗号分隔;
- { ... }包裹的代码块为函数体,可包含多条语句或为空。
注意:函数体内执行至return语句时立即结束并返回结果,内部可通过条件判断与循环实现复杂逻辑。
若函数无return语句,执行完毕后仍返回结果,值为undefined。
JavaScript中函数属于对象类型,上述abs()函数实为函数对象,abs变量名指向该对象。
因此,函数的另一种定义方式如下:
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
此方式中,function (x) { ... }为匿名函数(无名称),但被赋值给变量abs,可通过abs变量调用。
两种定义方式完全等价,注意第二种方式需遵循完整语法,在函数体末尾添加分号以表示赋值语句结束。
函数调用与参数传递
函数调用方法
调用函数时,按顺序传入参数即可:
abs(10); // 返回10
abs(-9); // 返回9
JavaScript允许传入任意数量参数而不影响调用,即使传入参数多于定义参数,函数内部无需使用这些多余参数:
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
若传入参数少于定义数量:
abs(); // 返回NaN
此时abs(x)中的参数x值为undefined,计算结果为NaN。
为避免接收到undefined,可对参数进行校验:
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
arguments对象的使用
arguments对象
JavaScript提供关键字arguments,仅在函数内部有效,始终指向调用者传入的所有参数。arguments类似Array但非Array实例:
function foo(x) {
alert(x); // 10
for (var i=0; i<arguments.length; i++) {
alert(arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
利用arguments可获取调用者传入的所有参数。即使函数未定义任何参数,仍可获取参数值:
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
实际上arguments常用于判断传入参数个数,常见写法如下:
// foo(a[, b], c)
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 实际拿到的参数是a和b,c为undefined
c = b; // 把b赋给c
b = null; // b变为默认值
}
// ...
}
若需将中间参数b设为“可选”,须通过arguments判断,重新调整参数并赋值。
ES6 rest参数详解
rest参数
因JavaScript函数允许接收任意参数,必须使用arguments获取所有参数:
function foo(a, b) {
var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i<arguments.length; i++) {
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
为获取已定义参数a、b之外的参数,需使用arguments并从索引2开始循环以排除前两个参数,此写法繁琐,旨在获取额外rest参数,是否存在更优方案?
ES6标准引入rest参数,上述函数可改写为:
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
rest参数必须置于末尾,前缀为...,运行结果显示:传入参数先绑定a、b,多余参数以数组形式赋给变量rest,无需arguments即可获取全部参数。
若传入参数未填满正常定义参数,无需担忧,rest参数将接收空数组(非undefined)。
因rest参数属ES6新标准,需测试浏览器兼容性。请使用rest参数编写sum()函数,接收任意参数并返回其和:
窗体顶端
'use strict';
// 测试:
var i, args = [];
for (i=1; i<=100; i++) {
args.push(i);
}
if (sum() !== 0) {
alert('测试失败: sum() = ' + sum());
} else if (sum(1) !== 1) {
alert('测试失败: sum(1) = ' + sum(1));
} else if (sum(2, 3) !== 5) {
alert('测试失败: sum(2, 3) = ' + sum(2, 3));
} else if (sum.apply(null, args) !== 5050) {
alert('测试失败: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args));
} else {
alert('测试通过!');
}
return语句与自动分号陷阱
注意return语句的使用
前文提及JavaScript引擎存在行末自动添加分号的机制,这可能导致return语句出现严重问题:
function foo() {
return { name: 'foo' };
}
foo(); // { name: 'foo' }
若将return语句拆分为两行:
function foo() {
return
{ name: 'foo' };
}
foo(); // undefined
需警惕,因JavaScript引擎在行末自动添加分号,上述代码实际变为:
function foo() {
return; // 自动添加了分号,相当于return undefined;
{ name: 'foo' }; // 这行语句已经没法执行到了
}
因此正确的多行写法应为:
function foo() {
return { // 这里不会自动加分号,因为{表示语句尚未结束
name: 'foo'
};
}
函数定义练习
练习题目
定义计算圆面积的函数area_of_circle(),包含两个参数:
r: 表示圆的半径;
pi: 表示π的值,若不传递则默认值为3.14