概述
通常来说,一个函数就是一个可以被外部代码调用(或者函数本身递归调用)的"子程序".和程序本身一样,一个函数的函数体是由一系列的语句组成的.函数可以接收传入参数,也可以返回一个返回值.
在JavaScript中,函数也是对象,可以像其他普通对象一样.添加属性和方法等.具体来讲,它们是Function
对象.
用法简介
在JavaScript中,每个函数其实都是一个Function
对象. 查看 Function
页面了解Function
对象的属性和方法.
要想返回一个返回值, 函数必须使用 return
语句指定所要返回的值. (使用new关键字调用一个构造函数除外).如果一个函数中没有使用return语句,则它默认返回undefined
.
调用函数时,传入函数的值称为函数的实参,对应位置的函数参数名称为形参.如果实参是一个包含原始值(数字,字符串,布尔值)的变量,则就算函数在内部改变了对应形参的值,返回后,该实参变量的值也不会改变.如果实参是一个对象引用,则对应形参会和该实参指向同一个对象.假如函数在内部改变了对应形参的值,返回后,实参指向的对象的值也会改变:
/* 定义函数 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',对象的属性已被修改. */ window.alert(mycar.brand);
在函数执行时,this
关键字并不会指向正在运行的函数本身,而是指向调用该函数的对象.所以,如果你想在函数内部获取函数自身的引用,只能使用函数名或者使用arguments.callee属性(严格模式下不可用),如果该函数是一个匿名函数,则你只能使用后者.
定义函数
定义函数一共有三种方法:
函数声明 (function
语句)
有一个特殊的语法来声明函数(查看function语句了解详情):
function name([param[, param[, ... param]]]) { statements }
-
name
- 函数名.
-
param
- 函数的参数的名称,一个函数最多可以有255个参数.
-
statements
- 这些语句组成了函数的函数体.
函数表达式 (function
操作符)
函数表达式和函数声明非常类似,它们甚至有相同的语法 (查看function操作符了解详情 ):
function [name]([param] [, param] [..., param]) { statements }
-
name
- 函数名,可以省略,省略函数名的话,该函数就成为了匿名函数.
-
param
- 函数的参数的名称,一个函数最多可以有255个参数.
-
statements
- 这些语句组成了函数的函数体.
Function
构造函数
和其他类型的对象一样, Function
对象也可以使用new
操作符来创建:
[new] Function (arg1, arg2, ... argN, functionBody)
-
arg1, arg2, ... argN
-
一个或多个变量名称,来作为函数的形参名.类型为字符串,值必须为一个合法的JavaScript标识符,例如Function("
x
", "y
","alert(x+y)"),或者逗号连接的多个标识符,例如Function("x,y","alert(x+y)")
-
functionBody
- 一个字符串,包含了组成函数的函数体的一条或多条语句.
即使不使用new
操作符,直接调用Function
函数,效果也是一样的,仍然可以正常创建一个函数.
Function
构造函数来创建函数.因为在这种情况下,函数体必须由一个字符串来指定.这样会阻止一些JS引擎做程序优化,还会引起一些其他问题.arguments
对象
在函数内部,你可以使用arguments
对象获取到该函数的所有传入参数. 查看 arguments.
Scope and the function stack
some section about scope and functions calling other functions
Recursion
A function can refer to and call itself. There are three ways for a function to refer to itself:
- the function's name
arguments.callee
- an in-scope variable that refers to the function
For example, consider the following function definition:
var foo = function bar() { // statements go here };
Within the function body, the following are all equivalent:
bar()
arguments.callee()
foo()
A function that calls itself is called a recursive function. In some ways, recursion is analogous to a loop. Both execute the same code multiple times, and both require a condition (to avoid an infinite loop, or rather, infinite recursion in this case). For example, the following loop:
var x = 0; while (x < 10) { // "x < 10" is the loop condition // do stuff x++; }
can be converted into a recursive function and a call to that function:
function loop(x) { if (x >= 10) // "x >= 10" is the exit condition (equivalent to "!(x < 10)") return; // do stuff loop(x + 1); // the recursive call } loop(0);
However, some algorithms cannot be simple iterative loops. For example, getting all the nodes of a tree structure (e.g. the DOM) is more easily done using recursion:
function walkTree(node) { if (node == null) // return; // do something with node for (var i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }
Compared to the function loop
, each recursive call itself makes many recursive calls here.
It is possible to convert any recursive algorithm to a non-recursive one, but often the logic is much more complex and doing so requires the use of a stack. In fact, recursion itself uses a stack: the function stack.
The stack-like behavior can be seen in the following example:
function foo(i) { if (i < 0) return; document.writeln('begin:' + i); foo(i - 1); document.writeln('end:' + i); } foo(3);
which outputs:
begin:3 begin:2 begin:1 begin:0 end:0 end:1 end:2 end:3
嵌套函数和闭包
你可以将一个函数嵌套在另一个函数内部.被嵌套的函数 (内部函数)是只属于嵌套它的函数(外部函数)的私有函数.这样就形成了一个闭包.
- A closure is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
Since a nested function is a closure, this means that a nested function can "inherit" the arguments and variables of its containing function. In other words, the inner function contains the scope of the outer function.
To summarize:
- The inner function can be accessed only from statements in the outer function.
- The inner function forms a closure: the inner function can use the arguments and variables of the outer function, while the outer function cannot use the arguments and variables of the inner function.
下例演示了一个嵌套函数:
function addSquares(a,b) { function square(x) { return x * x; } return square(a) + square(b); } a = addSquares(2,3); // returns 13 b = addSquares(3,4); // returns 25 c = addSquares(4,5); // returns 41
由于内部函数形成了一个闭包,你可以把这个内部函数当做返回值返回,该函数引用到了外部函数和内部函数的两个参数:
function outside(x) { function inside(y) { return x + y; } return inside; } fn_inside = outside(3); result = fn_inside(5); // returns 8 result1 = outside(3)(5); // returns 8
Preservation of variables
Notice how x
is preserved when inside
is returned. A closure must preserve the arguments and variables in all scopes it references. Since each call provides potentially different arguments, a new closure is created for each call to outside. The memory can be freed only when the returned inside
is no longer accessible.
This is not different from storing references in other objects, but is often less obvious because one does not set the references directly and cannot inspect them.
Multiply-nested functions
Functions can be multiply-nested, i.e. a function (A) containing a function (B) containing a function (C). Both functions B and C form closures here, so B can access A and C can access B. In addition, since C can access B which can access A, C can also access A. Thus, the closures can contain multiple scopes; they recursively contain the scope of the functions containing it. This is called scope chaining. (Why it is called "chaining" will be explained later.)
Consider the following example:
function A(x) { function B(y) { function C(z) { alert(x + y + z); } C(3); } B(2); } A(1); // alerts 6 (1 + 2 + 3)
In this example, C
accesses B
's y
and A
's x
. This can be done because:
B
forms a closure includingA
, i.e.B
can accessA
's arguments and variables.C
forms a closure includingB
.- Because
B
's closure includesA
,C
's closure includesA
,C
can access bothB
andA
's arguments and variables. In other words,C
chains the scopes ofB
andA
in that order.
The reverse, however, is not true. A
cannot access C
, because A
cannot access any argument or variable of B
, which C
is a variable of. Thus, C
remains private to only B
.
Name conflicts
When two arguments or variables in the scopes of a closure have the same name, there is a name conflict. More inner scopes take precedence, so the inner-most scope takes the highest precedence, while the outer-most scope takes the lowest. This is the scope chain. The first on the chain is the inner-most scope, and the last is the outer-most scope. Consider the following:
function outside() { var x = 10; function inside(x) { return x; } return inside; } result = outside()(20); // returns 20 instead of 10
The name conflict happens at the statement return x
and is between inside
's parameter x
and outside
's variable x
. The scope chain here is {inside
, outside
, global object}. Therefore inside
's x
takes precedences over outside
's x
, and 20 (inside
's x
) is returned instead of 10 (outside
's x
).
Function
constructor vs. function declaration vs. function expression
Compare the following:
- a function defined with the
Function
constructor assigned to the variablemultiply
var multiply = new Function("x", "y", "return x * y;");
- a function declaration of a function named
multiply
function multiply(x, y) { return x * y; }
- a function expression of an anonymous function assigned to the variable
multiply
var multiply = function(x, y) { return x * y; };
- a function expression of a function named
func_name
assigned to the variablemultiply
var multiply = function func_name(x, y) { return x * y; };
All do approximately the same thing, with a few subtle differences:
- There is a distinction between the function name and the variable the function is assigned to:
- The function name cannot be changed, while the variable the function is assigned to can be reassigned.
- The function name can be used only within the function's body. Attempting to use it outside the function's body results in an error (or
undefined
if the function name was previously declared via avar
statement). For example:var y = function x() {}; alert(x); // throws an error
The function name also appears when the function is serialized via
Function
's toString method.On the other hand, the variable the function is assigned to is limited only by its scope, which is guaranteed to include the scope where the function is declared in.
- As the 4th example shows, the function name can be different from the variable the function is assigned to. They have no relation to each other.
- A function declaration also creates a variable with the same name as the function name. Thus, unlike those defined by function expressions, functions defined by function declarations can be accessed by their name in the scope they were defined in:
function x() {} alert(x); // outputs x serialized into a string
The following example shows how function names are not related to variables functions are assigned to. If a "function variable" is assigned to another value, it will still have the same function name:
function foo() {} alert(foo); // alerted string contains function name "foo" var bar = foo; alert(bar); // alerted string still contains function name "foo"
- A function defined by '
new
Function'
does not have a function name. However, in the SpiderMonkey JavaScript engine, the serialized form of the function shows as if it has the name "anonymous". For example,alert(new Function())
outputs:function anonymous() { }
Since the function actually does not have a name,
anonymous
is not a variable that can be accessed within the function. For example, the following would result in an error:var foo = new Function("alert(anonymous);"); foo();
- Unlike functions defined by function expressions or by the
Function
constructor, a function defined by a function declaration can be used before the function declaration itself. For example:foo(); // alerts FOO! function foo() { alert('FOO!'); }
- A function defined by a function expression inherits the current scope. That is, the function forms a closure. On the other hand, a function defined by a
Function
constructor does not inherit any scope other than the global scope (which all functions inherit). - Functions defined by function expressions and function declarations are parsed only once, while those defined by the
Function
constructor are not. That is, the function body string passed to theFunction
constructor must be parsed every time it is evaluated. Although a function expression creates a closure every time, the function body is not reparsed, so function expressions are still faster than "new Function(...)
". Therefore theFunction
constructor should be avoided whenever possible.
It should be noted, however, that function expressions and function declarations nested within the function generated by parsing aFunction constructor
's string aren't parsed repeatedly. For example:var foo = (new Function("var bar = \'FOO!\';\nreturn(function() {\n\talert(bar);\n});"))(); foo(); // The segment "function() {\n\talert(bar);\n}" of the function body string is no longer parsed.
A function declaration is very easily (and often unintentionally) turned into a function expression. A function declaration ceases to be one when it either:
- becomes part of an expression
- is no longer a "source element" of a function or the script itself. A "source element" is a non-nested statement in the script or a function body:
var x = 0; // source element if (x == 0) { // source element x = 10; // not a source element function boo() {} // not a source element } function foo() { // source element var y = 20; // source element function bar() {} // source element while (y == 10) { // source element function blah() {} // not a source element y++; // not a source element } }
Examples:
-
// function declaration function foo() {} // function expression (function bar() {}) // function expression x = function hello() {}
-
if (x) { // function expression function world() {} }
-
// function declaration function a() { // function declaration function b() {} if (0) { // function expression function c() {} } }
Conditionally defining a function
Functions can be conditionally defined using either //function statements// (an allowed extension to the ECMA-262 Edition 3 standard) or the Function
constructor. Please note that such function statements are no longer allowed in ES5 strict. Additionally, this feature does not work consistently cross-browser, so you should not rely on it.
In the following script, the zero
function is never defined and cannot be invoked, because 'if (0)
' evaluates its condition to false:
if (0) { function zero() { document.writeln("This is zero."); } }
If the script is changed so that the condition becomes 'if (1)
', function zero
is defined.
Note: Although this kind of function looks like a function declaration, it is actually a statement, since it is nested within another statement. See differences between function declarations and function expressions.
Note: Some JavaScript engines, not including SpiderMonkey, incorrectly treat any function expression with a name as a function definition. This would lead to zero
being defined, even with the always-false if
condition. A safer way to define functions conditionally is to define the function anonymously and assign it to a variable:
if (0) { var zero = function() { document.writeln("This is zero."); } }
函数和事件处理程序
在JavaScript中, DOM事件处理程序只能是函数(相反的,其他DOM规范的绑定语言还可以使用一个包含handleEvent方法的对象做为事件处理程序
)(译者注:这句话已经过时,目前在大部分非IE[6,7,8]的浏览器中,都已经支持一个对象作为事件处理程序). 在事件被触发时,该函数会被调用,一个 event 对象会作为第一个也是唯一的一个参数传入该函数.像其他参数一样,如果不需要使用该event
对象, 则对应的形参可以省略.
Possible event targets in a HTML document include: window
(Window
objects, including frames), document
(HTMLDocument
objects), and elements (Element
objects). In the HTML DOM, event targets have event handler properties. These properties are lowercased event names prefixed with "on", e.g. onfocus
. An alternate and more robust way of adding event listeners is provided by DOM Level 2 Events.
Note: Events are part of the DOM, not of JavaScript. (JavaScript merely provides a binding to the DOM.)
The following example assigns a function to a window's "focus" event handler.
window.onfocus = function() { document.body.style.backgroundColor = 'white'; };
If a function is assigned to a variable, you can assign the variable to an event handler. The following code assigns a function to the variable setBGColor
.
var setBGColor = new Function("document.body.style.backgroundColor = 'white';");
You can use this variable to assign a function to an event handler in several ways. Here are two such ways:
- scripting with DOM HTML event properties
document.form1.colorButton.onclick = setBGColor;
- HTML event attribute
<input name="colorButton" type="button" value="Change background color" onclick="setBGColor();"/>
An event handler set this way is actually a function, named after the attribute, wrapped around the specified code. This is why the parenthesis in "
setBGColor()
" are needed here (rather than just "setBGColor
"). It is equivalent to:document.form1.colorButton.onclick = function onclick(event) { setBGColor(); };
Note how the event object is passed to this function as parameter
event
. This allows the specified code to use the Event object:<input ... onclick="alert(event.target.tagName);"/>
Just like any other property that refers to a function, the event handler can act as a method, and this
would refer to the element containing the event handler. In the following example, the function referred to by onfocus
is called with this
equal to window
.
window.onfocus();
A common JavaScript novice mistake is appending parenthesis and/or parameters to the end of the variable, i.e. calling the event handler when assigning it. Adding those parenthesis will assign the value returned from calling the event handler, which is often undefined
(if the function doesn't return anything), rather than the event handler itself:
document.form1.button1.onclick = setBGColor();
To pass parameters to an event handler, the handler must be wrapped into another function as follows:
document.form1.button1.onclick = function() { setBGColor('some value'); };
函数内的局部变量
arguments: 一个"类数组"的对象,包含了传入当前函数的所有实参.
arguments.callee : 指向当前正在执行的函数.
arguments.caller : 指向调用当前正在执行的函数的函数,请使用arguments.callee.caller
代替
arguments.length: 传入当前函数的实参个数.
例子
例子: 给一个数字左边补零
下面的函数返回一个字符串,其中包含了一个指定的数字和若干个计算出的补零。
// 该函数返回一个数字补零后的字符串形式 function padZeros(num, totalLen) { var numStr = num.toString(); // 返回值为一个字符串 var numZeros = totalLen - numStr.length; // 计算补零的数量 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) { // 调用 noFunc() } else { // 进行其他动作 }
注意在if语句中
,使用的是对noFunc
函数的引用,只有函数名,没有后面的括号"()",这样函数才不会被调用.