通常来说,一个函数就是一个可以被外部代码调用(或者函数本身递归调用)的"子程序"。和程序本身一样,一个函数的函数体是由一系列的语句组成的。函数可以接收传入参数,也可以返回一个值。
在JavaScript中,函数是第一等对象,不仅因为它既可以像普通对象一样拥有属性和方法,而且重要的是它可以被调用。简单来说它们是Function
对象。
更多的例子和说明,参见 JavaScript的指南的函数部分。
描述
在JavaScript中,每个函数其实都是一个Function
对象。查看Function
页面了解其
属性和方法。
函数不同于过程。函数总是会返回一个值,但是一个过程有可能返回一个值,也有可能不返回。
如果一个函数中没有使用return语句,则它默认返回undefined
。要想返回一个特定的值,则函数必须使用 return
语句来指定一个所要返回的值。(使用new关键字调用一个构造函数除外)。
调用函数时,传递给函数的值被称为函数的实参(值传递),对应位置的函数参数名叫作形参。如果实参是一个包含原始值(数字,字符串,布尔值)的变量,则就算函数在内部改变了对应形参的值,返回后,该实参变量的值也不会改变。如果实参是一个对象引用,则对应形参会和该实参指向同一个对象。假如函数在内部改变了对应形参的值,返回后,实参指向的对象的值也会改变:
/* 定义函数 myFunc */ function myFunc(theObject) { //实参 mycar 和形参 theObject 指向同一个对象. theObject.brand = "Toyota"; } /* * 定义变量 mycar; * 创建并初始化一个对象; * 将对象的引用赋值给变量 mycar */ var mycar = { brand: "Honda", model: "Accord", year: 1998 }; /* 弹出 'Honda' */ window.alert(mycar.brand); /* 将对象引用传给函数 */ myFunc(mycar); /* * 弹出 'Toyota',对象的属性已被修改. */ console.log(mycar.brand);
在函数执行时,this
关键字并不会指向正在运行的函数本身,而是指向调用该函数的对象。所以,如果你想在函数内部获取函数自身的引用,只能使用函数名或者使用arguments.callee属性(严格模式下不可用),如果该函数是一个匿名函数,则你只能使用后者。
函数定义
定义函数有多种方法:
函数声明 (函数语句)
有一个特殊的语法来声明函数(查看函数语句了解详情):
function name([param[, param[, ... param]]]) { statements }
name
- 函数名.
param
- 传递给函数的参数的名称,一个函数最多可以有255个参数。
statements
- 组成函数体的声明语句。
函数表达式 (function
expression)
函数表达式和函数声明非常相似,它们甚至有相同的语法 (查看函数表达式了解详情 ):
function [name]([param] [, param] [..., param]) { statements }
name
- 函数名,可以省略。当省略函数名的时候,该函数就成为了匿名函数。
param
- 传递给函数的参数的名称,一个函数最多可以有255个参数.
statements
- 组成函数体的声明语句。
函数生成器声明 (function*
语句)
注意:函数生成器是ECMAScript 6新定义的,大部分浏览器尚不支持。
函数声明有一种特殊的语法 (详情请查阅function* statement
):
function* name([param[, param[, ...param]]]) { statements }
name
- 函数名称.
param
- 传递给函数的参数的名称,一个函数最多可以有255个参数。
statements
- 组成函数体的声明语句。
函数生成器表达式 (function*
表达式)
注意: 函数生成器是ECMAScript 6新定义的,大部分浏览器尚不支持。
构造函数表达式和函数声明类似,并且有着相同的语法 (详情请查阅 function* expression
):
function* [name]([param] [, param] [..., param]) { statements }
name
- 函数名称。函数名可以被省略,在这种情况下该函数将变成匿名函数。
param
- 传递给函数的参数的名称。一个函数可以有多达255个参数
statements
- 组成函数体的声明语句。
箭头函数表达式 (=>)
Note: 箭头函数表达式是ECMAScript 6新定义的,大部分浏览器尚不支持。
箭头函数表达式有着更短的语法,并在词汇方面结合这个值 (详情请查阅 arrow functions ):
([param] [, param]) => { statements } param => expression
param
- 参数名称. 零参数需要用()表示. 只有一个参数时不需要括号. (例如
foo => 1
) statements or expression
- 多个声明statements需要用大括号括起来,而单个表达式时则不需要。表达式expression也是该函数的隐式返回值。
Function
构造函数
注意: 不推荐使用 Function
构造函数创建函数,因为它需要的函数体作为字符串可能会阻止一些JS引擎优化,也会引起其他问题。
所有其他对象, Function
对象可以用new操作符创建:
new Function (arg1, arg2, ... argN, functionBody)
arg1, arg2, ... argN
- 函数使用零个或多个名称作为正式的参数名称。每一个必须是一个符合有效的JavaScript标识符规则的字符串或用逗号分隔的字符串列表,例如“x”,“theValue”或“a,b”。
functionBody
- 一个构成的函数定义的,包含JavaScript声明语句的字符串。
把Function的构造函数当作函数一样调用(不使用new操作符)的效果与作为Function的构造函数调用一样。
生成器函数的构造函数
注意: GeneratorFunction
不是一个全局对象,但可以从构造函数实例取得。(详情请查阅生成器函数).
注意: 不推荐使用构造器函数的构造函数 (GeneratorFunction
constructor)创建函数,因为它需要的函数体作为字符串可能会阻止一些JS引擎优化,也会引起其他问题。
所有其他对象, GeneratorFunction
对象可以用 new 操作符创建:
new GeneratorFunction (arg1, arg2, ... argN, functionBody)
arg1, arg2, ... argN
- 函数使用零个或多个名称作为正式的参数名称。每一个必须是一个符合有效的JavaScript标识符规则的字符串或用逗号分隔的字符串列表,例如“x”,“theValue”或“a,b”。
functionBody
- 一个构成的函数定义的,包含JavaScript声明语句的字符串。
把Function的构造函数当作函数一样调用(不使用new操作符)的效果与作为Function的构造函数调用一样。
Function 参数
注意: 默认(Default)的和其它(Rest)的参数是ECMAScript 6新定义的,浏览器尚未普遍支持。
默认(Default)参数
如果没有值或传入了未定义的值,默认函数参数允许形式参数使用默认值初始化。 详情请查阅:default parameters.
其它(Rest)参数
其它(rest)参数语法允许将数量不限的参数(arguments)描述成一个数组。 详情请查阅: rest parameters.
参数(arguments)对象
你可以参阅在函数里使用arguments对象的函数参数。查阅: arguments.
arguments
: 一个包含了传递给当前执行函数参数的类似于数组的对象。arguments.callee
: 当前正在执行的函数。arguments.caller
: 调用当前执行函数的函数。arguments.length
: 传给函数的参数的数目。
方法函数定义
Getter 和 setter 函数
你可以在支持添加新属性的任何标准的内置对象或用户定义的对象内定义getter(访问方法)和setter(设置方法)。使用对象字面量语法定义getters和setters方法。
方法定义语法
注意: 方法定义是ECMAScript 6新定义的,浏览器尚未普遍支持。
从ECMAScript 6开始, 你可以用更短的语法定义自己的方法,类似于getters和setters。详情请查阅 method definitions .
var obj = { foo() {}, bar() {} };
Function构造函数 vs 函数声明 vs 函数表达式
对比下面的例子:
一个用Function构造函数定义的函数,被赋值给变量multiply:
function multiply(x, y) { return x * y; }
一个匿名函数的函数表达式,被赋值给变量multiply:
var multiply = function(x, y) { return x * y; };
一个命名为func_named的函数的函数表达式,被赋值给变量multiply:
var multiply = function func_name(x, y) { return x * y; };
差别
虽然有一些细微的差别,但所起的作用都差不多:
函数名和函数的变量存在着差别。函数名不能被改变,但函数的变量却能够被再分配。函数名只能在函数体内使用。倘若在函数体外使用函数名将会导致错误(如果函数之前是通过一个var语句声明的则是undefined)。例如:
var y = function x() {}; alert(x); // throws an error
当函数是通过 Function
's toString method被序列化时,函数名同样也会出现。
另一方面,被函数赋值的变量仅仅受限于它的作用域,该作用域确保包含着该函数被声明时的作用域。
正如第四个例子所展示的那样,函数名与被函数赋值的变量是不相同的. 彼此之间没有关系。函数声明同时也创建了一个和函数名相同的变量。因此,与函数表达式定义不同,以函数声明定义的函数能够在它们被定义的作用域内通过函数名而被访问到:
使用用 'new Function'定义的函数没有函数名。
然而,在 SpiderMonkey JavaScript引擎中,其函数的序列化形式表现的好像它拥有一个名叫"anonymous"的名称一样。比如,使用 alert(new Function())
输出:
function anonymous() { }
而实际上其函数并没有名称,anonymous
不是一个可以在函数内被访问到的变量。例如,下面的例子将会导致错误:
var foo = new Function("alert(anonymous);"); foo(); // undefined
和通过函数表达式定义或者通过Function构造函数定义的函数不同,函数声明定义的函数可以在它被声明之前使用。举个例子:
foo(); // alerts FOO! function foo() { alert('FOO!'); }
函数表达式定义的函数继承了当前的作用域。换言之,函数构成了闭包。另一方面,Function构造函数定义的函数不继承任何全局作用域以外的作用域(那些所有函数都继承的)。
通过函数表达式定义的函数和通过函数声明定义的函数只会被解析一次,而Function构造函数定义的函数却不同。也就是说,每次构造函数被调用,传递给Function构造函数的函数体字符串都要被解析一次 。虽然函数表达式每次都创建了一个闭包,但函数体不会被重复解析,因此函数表达式仍然要快于"new Function(...)
"。 所以Function构造函数应尽可能地避免使用。
有一点应该要注意的,在通过解析Function构造函数字符串产生的函数里,内嵌的函数表达式和函数声明不会被重复解析。例如:
var foo = (new Function("var bar = \'FOO!\';\nreturn(function() {\n\talert(bar);\n});"))(); foo(); // 函数体字符串"function() {\n\talert(bar);\n}"的这一部分不会被重复解析。
函数声明非常容易(经常是意外地)转换为函数表达式。当它不再是一个函数声明:
- 成为表达式的一部分
- 不再是函数或者script自身的“源元素” (source element)。在script或者函数体内“源元素”并非是内嵌的语句(statement)。
var x = 0; // source element if (x == 0) { // source element x = 10; // 非source element function boo() {} // 非 source element } function foo() { // source element var y = 20; // source element function bar() {} // source element while (y == 10) { // source element function blah() {} // 非 source element y++; //非source element } }
例子
// 函数声明 function foo() {} // 函数表达式 (function bar() {}) // 函数表达式 x = function hello() {} if (x) { // 函数表达式 function world() {} } // 函数声明 function a() { // 函数声明 function b() {} if (0) { //函数表达式 function c() {} } }
有条件地定义一个函数
可以有条件地使用函数表达式( ECMA-262 Edition 3 标准允许的一个扩展) 或者Function构造函数来定义函数。请注意ES5已经不允许在严格模式下使用函数表达式 。此外,该特性在跨浏览器间不能一致地工作,因此你不应该依赖它。
在下面的script中,zero
函数永远不会被定义和调用,因为'if (0)'的运算结果是false:
if (0) { function zero() { document.writeln("This is zero."); } }
如果script的条件改为'if (1)
',那么zero函数就能够被定义。
注意:虽然这类函数看起来像是函数声明,但它实际上是表达式(或者语句),因为它是内嵌于另外一个语句当中的。来看看函数声明和函数表达式之间的不同。
注意:一些JavaScript引擎, SpiderMonkey除外,不能正确地处理任何带有名称的函数表达式的函数定义,这将导致即使条件结果永远为false,而zero函数都会被定义。有条件地定义一个函数的一个更安全的方法是定义一个匿名函数并将它赋值给一个变量:
if (0) { var zero = function() { document.writeln("This is zero."); } }
示例
返回格式化数字
下面的函数返回一个字符串,其中包含了一个格式化的、以一个由0开头并填充的数字。
// 这个函数返回一个由0开头并填充的字符串 function padZeros(num, totalLen) { var numStr = num.toString(); // 用字符串返回值进行初始化 var numZeros = totalLen - numStr.length; // 计算zeros顺序 for (var i = 1; i <= numZeros; i++) { numStr = "0" + numStr; } return numStr; }
下面的语句调用了padZeros函数:
var result; result = padZeros(42,4); // returns "0042" result = padZeros(42,2); // returns "42" result = padZeros(5,4); // returns "0005"
检测函数是否存在
你可以通过 typeof 操作符检测一个函数是否存在。在下面的例子中,用一个测试来演示检测window对象是否拥有一个noFunc函数的属性。如果存在,那就使用它;否则就采取其它的一些操作。
if ('function' == typeof window.noFunc) { // use noFunc() } else { // do something else }
注意在 if语句中,使用了noFunc的引用--在函数名的后面没有括号“()”,所以实际函数并没有被调用。
规范
规范 | 状态 | 说明 |
---|---|---|
ECMAScript 1st Edition (ECMA-262) | Standard | 初始定义. Implemented in JavaScript 1.0 |
ECMAScript 5.1 (ECMA-262) Function Definition |
Standard | |
ECMAScript 2015 (6th Edition, ECMA-262) Function definitions |
Standard | 新定义的: Arrow functions, Generator functions, default parameters, rest parameters |
ECMAScript 2016 Draft (7th Edition, ECMA-262) Function definitions |
Draft |
浏览器兼容性
特性 | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
基本支持 | (Yes) | (Yes) | (Yes) | (Yes) | (Yes) |
Generator function | 39 | 26.0 (26.0) | ? | 26 | ? |
箭头函数 | 未实现 | 22.0 (22.0) | 未实现 | 未实现 | 未实现 |
特性 | Android | Chrome for Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile |
---|---|---|---|---|---|---|
基本支持 | (Yes) | (Yes) | (Yes) | (Yes) | (Yes) | (Yes) |
Generator function | ? | 39 | 26.0 (26.0) | ? | 26 | ? |
箭头函数 | 未实现 | 未实现 | 22.0 (22.0) | 未实现 | 未实现 | 未实现 |