在JavaScript中,函数是一等对象。它即可以像普通对象一样有属性和方法,又可以被外部程序或自身调用。JavaScript中所有的函数都是一个Function对象。
1. 函数介绍
定义
通常来讲,函数是一个可以被外部代码调用或者函数本身递归调用的子程序。函数由函数体、传入参数、返回值三部分组成。一个函数的函数体由一系列的语句组成,函数可以接收传入参数,也可以返回一个值。
参数
参数分为形参和实参。其中,调用函数时,传递给函数的值被称为函数的实参;而对应位置的函数参数名叫作形参。
如果实参是一个包含原始类型的变量(Undefined、Null、Boolean、Number、String和Symbol),那么在即使在函数内部修改对应形参的值,返回后,该实参变量的值也不会改变。如果实参是一个引用类型(Object),则在则在函数内部修改对应形参的值后,实参值也会发生变化,因为形参和该实参指向同一个对象。
如,使用引用类型实参时:
// 定义一个函数 myFunc
function myFunc(theObject)
{
//实参 itbilu 和形参 theObject 指向同一个对象.
theObject.name = "itbilu";
}
/*
* 定义变量 itbilu
* 并将其初始化为一个对象
*/
var itbilu = {
name: "IT笔录",
domain: "http://niefengjun.cn",
year: 2015
};
// 'IT笔录'
console.log(mycar.name);
// 对象引用传给函数
myFunc(itbilu);
// 'itbilu',对象的属性已被修改.
console.log(mycar.name);
返回值
函数总是会有一个返回值。函数一般使用return关键字返回一个值,如果函数中没有使用return语句,那会默认返回undefined。如果不要返回undefined,则在函数中必须使用return语句来指定所要返回的值。(使用new关键字调用一个构造函数除外)。
2. 函数定义
定义函数一个函数时,可以使用函数声明、函数表达式、Function构造函数
三种基本方式来定义定义JavaScript函数。而在ECMAScript 2015语言标准中,又扩展了Generator(生成器)函数、箭头(=>)函数两种函数,所以在JavaScript中有以下定义函数的方式。
2.1 函数声明
函数声明(函数语名句)是指用指定的参数和函数体声明一个函数。使用函数声明定义一个函数语法如下:
function name([param,[, param,[..., param]]]) {
[statements]
}
name- 函数名param- 传递给函数的参数,最多有255个statements- 函数体
2.2 函数表达式
函数表达式(function expression)和函数声明非常相似,不同的是函数表达式可以省略函数名,从而定义一个匿名函数。函数表达式语法结构如下:
function [name]([param1[, param2[, ..., paramN]]]) {
statements
}
name- 函数名。可以省略,省略时即为匿名函数。param- 传递给函数的参数,最多有255个statements- 函数体
在使用函数表达式时,函数名(name)可有可无,可以省略函数名从而创建匿名函数。而无论有没有函数名,该函数名都不能在函数外部被调用,只能在函数体内部调用函数名。
如,使用函数表达式定义函数:
var x = function(y) {
return y * y;
};
2.3 Function构造函数
和其它JavaScript内置对象类似,Function也可以使用new关键字创建对象实例:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
arg1, arg2, ... argN- 函数使用的参数functionBody- 一个表示函数定义的JavaScript语句的字符串。
注意:Function构造函数使用字符串做为函数体,这会阻止JS引擎的优化并带来一些其它问题。因此,并不建议使用这种函数定义方式。
更多关于函数声明、函数表达式、构造函数之间的比较,请参考:函数声明、Function构造函数、函数表达式的比较
2.4 生成器函数声明
生成器函数(Generator)是ES6中新增的一种函数,其声明方式为function*,即在function关键字后增加一个*号。
生成器函数声明语法格式如下:
function* name([param[, param[, ...param]]]) {
statements
}
name- 函数名param- 传递给函数的参数,最多有255个statements- 函数体
2.5 生成器函数表达式
生成器函数同样可以使用表达式定义的方式:
function* [name]([param] [, param] [..., param]) {
statements
}
name- 函数名。可以省略,省略时即为匿名函数。param- 传递给函数的参数,最多有255个statements- 函数体
2.6 生成器函数构造函数
生成器函数同样有构造函数的定义方式,生成器函数的构造函数是GeneratorFunction,可以使用new关键字来创建对象:
new GeneratorFunction (arg1, arg2, ... argN, functionBody)
arg1, arg2, ... argN- 函数使用的参数functionBody- 一个表示函数定义的JavaScript语句的字符串。
注意:不推荐使用构造器函数GeneratorFunction来创建函数,因为它需要的函数体作为字符串做为函数体,可能会阻止一些JS引擎优化,也会引起其他问题。
2.7 箭头函数表达式
箭头函数是简写形式的函数表达式,并且它拥有词法作用域的this值(即不会新产生自己作用域下的this,、arguments、super、new.target等对象),箭头函数总是匿名函数。
箭头函数的语法结构如下:
([param] [, param]) => { statements } param => expression
param- 函数参数statements 或 expression- 声明多个语句时需要用大括号括起来,而单个表达式则不用。
3. arguments对象与ES6新增的参数表示方法
arguments是一个类似数组的对象,表示传递给一个函数的参数列表。arguments不是全局的,而是一个是函数内部的本地变量。在ES6语言标准中,对参数对象进行了扩展,增加了剩余(reset)参数和默认(default)参数两种参数表示形式。
3.1 arguments对象
在JavaScript中,函数(function)参数被表示为一个类似数组(Array)对象的arguments对象。该对象是一个参数列表,通过这个对象你可以向函数体传入已指定参数之外的额外参数,也就是说,你可以在创建参数时不指定任何参数而通过arguments对象对象访问所要传入的参数。
我们可以在函数内部使用arguments关键字,来获取该函数的参数。而访问一个函数参数,可以使用形参和arguments两种形式:
function myFun(arg1, arg2) {
console.log(arg1);
console.log(arg2);
console.log(arguments[0]);
console.log(arguments[1]);
}
myFun('niefengjun.cn', 'IT笔录');
//niefengjun.cn
//IT笔录
//niefengjun.cn
//IT笔录
arguments在对Function对象调用apply()方法时非常有用:
function show (site, domain) {
console.log('%s-%s', site, domain);
}
function itbilu (){
show.apply(this, arguments);
}
itbilu('IT笔录', 'niefengjun.cn'); //IT笔录-niefengjun.cn
arguments对象并不是一个真正的Array,它并没有数组所特有的属性和方法。如,没有.pop方法。但可以将其转换为一个数组:
var args = Array.prototype.slice.call(arguments);
3.2 默认参数
在JavaScript中,如果函数的实参未传入时,则参数的默认值是undefined。有时,我们可能需要设置一个不同的默认值,这时可以使用默认参数。
默认参数(默认值参数)又称做默认函数参数,是指如果一个形参没有传入对应的实参或者传入了undefined,则该形参会被赋予一个默认值。
如,在下面的乘法计算中,如果参数b未提供,那么其值是undefined,直接进行乘法运算的值会是NaN。所以需要对b的值进行判断,并将其值赋为1:
而如果使用默认值参数,我们就可以省去这些判断:
function multiply(a, b = 1) {
return a*b;
}
multiply(5); // 5
3.3 剩余参数
剩余(reset)参数是将函数长度不确定的实参表示为一个数组,并以...参数名的形式做为函数最后一个参数提供。
可以像下面这样使用剩余参数:
function f(a, b, ...theArgs) {
console.log(theArgs); // [3, 4, 5]
}
f(1, 2, 3, 4, 5);
剩余参数与arguments对象有以下两点区别:
- 剩余参数只包含没有提供形参的实参;而
arguments包含了提供给函数的所有实参 arguments不是一个真正的数组;而剩余参数是一个Array对象
4. 定义方法函数
方法函数是指定义在对象内部的函数,对象方法分为属性方法和访问器方法(Getter、Setter)。在ES6之前,只能使用Object.defineProperty()来定义访问器方法。而在ES6中增加了简短方法定义方式,可以在对象初始器中定义属性方法的语法,同样 这是一种把方法名直接赋给函数的简写方式。
4.1 对象的Getter和Setter函数
在任何支持添中新属性的JavaScript标准的内置对象或用户定义的对象中,都可以定义访问器方法getter和setter。访问器方法,是对象提供给外部的用于访问或设置对象内置属性的访问接口。getter是访问器,它是一个访问方法,用于访问对象的内置属性。setter是设置器,它是一个设置方法,用于设置对象的内置属性。内定义getter(访问方法)和setter(设置方法)。
在ES6之前,我们会像下面这样定义访问器和设置器:
var site = {
_name: 'IT笔录'
};
Object.defineProperty(site, "name", {
get: function() {
return this._name;
},
set: function(value) {
this._name = value;
}
});
而在ES6及之后,我们可以像下面这样定义访问器和设置器:
var site = {
_name: 'IT笔录',
get name() {
return this._name;
},
set name(value) {
this._name = value;
}
};
site.name = 'http://niefengjun.cn';
site.name; // 'http://niefengjun.cn'
在ES6中,还增加了属性名表达式。在定义访问器方法时,同样可以使用属性名表达式:
var log = ['test'];
var obj = {
get latest () {
if (log.length == 0) return undefined;
return log[log.length - 1]
}
}
obj.latest; // "test"
4.2 ES6中的对象方法定义
与getter和setter类似,从ECMAScript 6开始, 我们可以用更简短的语法定义自己的方法。
在ES6之前,我们会像下面这样定义对象方法:
var obj = {
foo: function() {},
bar: function() {}
};
而在ES6之后,定义对象方法可以更加简单:
var obj = {
foo() {},
bar() {}
};
5. 块级函数、条件定义函数
5.1 块级函数
块级函数是指定在一个语句块中的函数。
非严格模式下,ES6之前与ES6及之后的块级函数定义没有什么区别。但并不建议在非严格模式下定义,因为非严格模式下的块级函数形为比较奇怪和不可控。
在严格模式下,在ES6之前,是禁止定义块级函数的,如果定义块级函数会抛出SyntaxError异常。ES6及之后,严格模式下定义的块级函数其作用域也被限制在所定义的块中:
'use strict';
function f() {
return 1;
}
{
function f() { // ES6之前会抛出 SyntaxError
return 2;
}
f(); // 2 // ES6 及之后
}
f() === 1; // true
// f() === 2 // 非严格模式下 2
5.2 条件定义函数
条件定义函数类似于块级函数的定义。对于以下函数定义来说,由于'if (0)'的运算结果为false,所以zero函数永远不会被定义。而判断条件被换成'if (1)'就会被定义:
if (0) {
function zero() {
document.writeln("This is zero.");
};
zero();
}
一些JavaScript引擎不能很好的处理任何带有名称的函数表达式的函数定义,因此zero是否会被定义带有不确认性。有条件地定义函数的一个更安全的方法是,定义一个匿名函数并将它赋值给一个变量:
if (0) {
var zero = function() {
document.writeln("This is zero.");
};
zero();
}
