在JavaScript中,我们可以通过多种方式来定义一个函数。下面,我们就对这些定义函数的方式分别进行说明。
Function构造器
利用Function
构造器可以创建一个新的Function
对象。但是通过构造器动态创建函数,存在安全性和性能方面的问题。这种方式,我们很少,或基本上不会用到。
var addition = new Function('a', 'b', 'return a + b;');
console.log('函数名:' + addition.name); // anonymous
console.log(addition(1, 2)); // 3
此外,我们也可以将Function
构造器当作函数调用(不使用new
)来创建函数,这和使用new
关键字的效果是一模一样的:
var addition = Function('a', 'b', 'return a + b;');
console.log('函数名:' + addition.name); // anonymous
console.log(addition(1, 2)); // 3
每个JavaScript函数实际上都是一个Function
对象。我们可以通过以下代码进行验证:
(function(){}).constructor === Function // true
(()=>'').constructor === Function // true
函数声明
函数声明是我们常用的一种定义函数的方式。
function addition(a, b) {
return a + b;
}
console.log('函数名:' + addition.name); // 函数名:addition
console.log(addition(1, 2)); // 3
注意,函数声明会被提升到封闭函数或全局范围的顶部。即我们可以在函数声明之前使用该函数:
console.log('函数名:' + addition.name); // 函数名:addition
console.log(addition(1, 2)); // 3
function addition(a, b) {
return a + b;
}
函数表达式
函数表达式使得我们可以在表达式中定义函数。这也是我们极其常用的一种定义函数表达式的方式。
var addition = function (a, b) {
return a + b;
};
console.log('函数名:' + addition.name); // 函数名:addition
console.log(addition(1, 2)); // 3
这种定义函数的方式也叫函数直接量。
像上面我们是将一个匿名函数赋值给一个变量。此外,我们还可以使用一个命名函数赋值给一个变量:
var addition = function add(a, b) {
return a + b;
};
console.log('函数名:' + addition.name); // 函数名:add
console.log(addition(1, 2)); // 3
console.log('函数名:' + add(1, 2)); // Uncaught ReferenceError: add is not defined
注意,这里的函数名add
只能在函数体中使用,常用作递归调用:
var i = 0;
var recursion = function call() {
console.log(++i);
if (i < 3) {
call();
}
};
recursion(); // 1 2 3
当然,我们也可以使用变量名addition
作为递归调用:
var i = 0;
var recursion = function call() {
console.log(++i);
if (i < 3) {
recursion();
}
};
recursion(); // 1 2 3
函数表达式与函数声明很类似,它们的主要区别是:
- 函数表达式不存在被提升(不可以在函数表达式之前调用它),而函数声明会被提升
- 函数表达式可以用作IIFE(Immediately Invoked Function Expression)
箭头函数
箭头函数是ES2015开始引入的一种新的定义函数的方式。我们可以将箭头函数看作是一种简写紧凑型的“函数表达式”,它和函数表达式一样都不会被提升。但与传统的函数表达式相比,箭头函数有以下几个不同点:
- 箭头函数不绑定
this
、arguments
- 箭头函数不能用作构造函数,即不能使用
new
来创建对象 - 箭头函数没有
prototype
var addition = (a, b) => {
return a + b;
};
console.log('函数名:' + addition.name); // 函数名:addition
console.log(addition(1, 2)); // 3
当函数体中只有return
语句时,我们还可以使用“简洁体”代替常规的“块体”:
var addition = (a, b) => a + b;
console.log('函数名:' + addition.name); // 函数名:addition
console.log(addition(1, 2)); // 3
但是注意,如果函数体中返回的是对象字面量时,需要把对象字面量包裹在小括号中。否则,大括号中的代码会被解析为代码(对象的属性名会被解析为标签)。
比如下面的函数体会被解析为代码运行,故没有返回值:
var func = () => { foo: 1 };
console.log(func()); // undefined
又比如下面的函数体因被解析为代码,故其中的函数声明必须要指定函数名称:
var func = () => { foo: function() {} };
// Uncaught SyntaxError: Function statements require a function name
对于上面的两个例子,正确的写法是用小括号包裹函数体。
例子一:
var func = () => ({ foo: 1 });
console.log(func()); // {foo: 1}
例子二:
var func = () => ({ foo: function() {} });
console.log(func()); // {foo: ƒ}
ES2015还引入了
class
关键字,虽然它的实现只是一个语法糖,但配合箭头函数使用,就可以让我们清楚地知道是在定义一个函数还是在定义一个类,这可以避免我们理解上的混乱。