一、函数的定义
function 函数名(参数列表)
{
// 函数体
}
示例:
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。
如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。
由于JavaScript的函数也是一个对象,上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。
因此,第二种定义函数的方式如下:
var 变量名 = function (参数列表)
{
// 函数体
};
示例:
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。
上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。
二、函数的调用
调用函数时,按顺序传入参数即可:
abs(10); // 返回10
abs(-9); // 返回9
1、传入的参数比定义的参数多也没有问题
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
2、传入的参数比定义的少也没有问题:
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;
}
}
JavaScript函数允许接收任意个参数。
三、函数的属性、方法
在JavaScript中,函数是第一类对象,这意味着函数具有属性和方法,就像其他对象一样。
3-1、函数的属性
1、length属性
这个属性表示函数期望的参数个数。对于非函数对象,该属性的值为undefined。
function example(a, b, c) {
// ...
}
console.log(example.length); // 输出 3
2、name属性
这个属性返回函数的名称。对于匿名函数,这个属性可能是一个空字符串。
function myFunction() {
// ...
}
console.log(myFunction.name); // 输出 "myFunction"
const anonymous = function() {
// ...
};
console.log(anonymous.name); // 输出可能是 "anonymous" 或者空字符串,取决于JavaScript引擎
3、prototype属性
这个属性是所有函数对象都有的,它指向一个对象,该对象可以用作构造新对象时的原型。通过修改函数的prototype属性,你可以为该函数创建的所有对象添加新属性或方法。
function MyObject() {
console.log('MyObject');
}
MyObject.prototype.myMethod = function() {
console.log('myMethod');
};
const obj = new MyObject(); // MyObject
obj.myMethod(); // myMethod 调用原型上的方法
4、其他自定义属性
也可以给函数添加自定义属性,就像给其他对象添加属性一样。
function greet() {
console.log('Hello!');
}
greet.customProperty = 'Some value';
console.log(greet.customProperty); // 输出 "Some value"
3-2、函数的方法
1、apply()方法
调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
function greet(name) {
console.log(`Hello, ${name}!`);
}
const args = ['World'];
greet.apply(null, args); // 输出 "Hello, World!"
2、call()方法
调用一个具有给定this值的函数,以及提供的参数列表。
function greet(name) {
console.log(`Hello, ${name}!`);
}
greet.call(null, 'World'); // 输出 "Hello, World!"
3、bind()方法
创建一个新的函数,当被调用时,它的this值被设置为提供的值,并且在调用新函数时,将预先传入的一组参数作为原函数的参数序列的前缀。
function greet(name) {
console.log(`Hello, ${name}!`);
}
const boundGreet = greet.bind(null, 'World');
boundGreet(); // 输出 "Hello, World!"
4、toString()方法
返回一个表示该函数源代码的字符串。
function greet() {
console.log('Hello!');
}
console.log(greet.toString()); // 输出函数的源代码字符串
5、给函数对象添加自定义方法
由于函数本身就是对象,你可以像给任何其他对象添加属性一样给函数添加方法。这允许你扩展函数的功能,并创建出更具表现力和可重用的代码。
function greet() {
console.log('Hello!');
}
// 自定义一个方法到 greet 函数对象上
greet.customMethod = function() {
console.log('This is a custom method on the greet function.');
};
// 调用 greet 函数
greet(); // 输出 "Hello!"
// 调用 greet 上的自定义方法
greet.customMethod(); // 输出 "This is a custom method on the greet function."
值得注意的是,虽然你可以给函数对象添加任意多的自定义属性和方法,但通常建议谨慎使用这种做法。过度扩展内置对象或常用库可能会导致代码难以理解和维护,也可能与未来的JavaScript版本或第三方库冲突。在创建自定义方法时,最好确保它们有明确的目的,并且与函数的原始功能相关。
如果你发现自己在多个地方重复使用相同的自定义方法,可能更好的做法是将这些方法组织到一个单独的对象或类中,然后通过组合或继承的方式使用它们。这样可以更好地组织你的代码,并使其更易于维护和扩展。
四、arguments关键字
JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array,但它不是一个Array:
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + 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最常用于判断传入参数的个数。
五、rest参数
为了获取除了已定义参数a、b之外的参数,我们不得不用arguments,并且循环要从索引2开始以便排除前两个参数:
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);
}
写法变扭,不方便,使用ES6标准引入的rest参数。因此,rest参数的目的:获取function中调用者传入的额外的参数。
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)。
四、return语句
JavaScript引擎有一个在行末自动添加分号的机制,所以,存在如下问题:
正确写法:
function foo() {
return { name: 'foo' };
}
foo(); // { name: 'foo' }
错误写法:
function foo() {
return
{ name: 'foo' };
}
foo(); // undefined
要小心了,由于JavaScript引擎在行末自动添加分号的机制,上面的代码实际上变成了:
function foo() {
return; // 自动添加了分号,相当于return undefined;
{ name: 'foo' }; // 这行语句已经没法执行到了
}
所以正确的多行写法是:
function foo() {
return { // 这里不会自动加分号,因为{表示语句尚未结束
name: 'foo'
};
}