Notice: Some MDN email addresses and hashed passwords were temporarily posted on a publicly accessible server. http://blog.mozilla.org/security/2014/08/01/mdn-database-disclosure/

您的搜索结果

    函数和函数作用域

    概述

    通常来说,一个函数就是一个可以被外部代码调用(或者函数本身递归调用)的"子程序".和程序本身一样,一个函数的函数体是由一系列的语句组成的.函数可以接收传入参数,也可以返回一个返回值.

    在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:

    1. the function's name
    2. arguments.callee
    3. 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:

    1. bar()
    2. arguments.callee()
    3. 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:

    1. B forms a closure including A, i.e. B can access A's arguments and variables.
    2. C forms a closure including B.
    3. Because B's closure includes A, C's closure includes A, C can access both B and A's arguments and variables. In other words, C chains the scopes of B and A 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:

    1. a function defined with the Function constructor assigned to the variable multiply
      var multiply = new Function("x", "y", "return x * y;");
      
    2. a function declaration of a function named multiply
      function multiply(x, y) {
         return x * y;
      }
      
    3. a function expression of an anonymous function assigned to the variable multiply
      var multiply = function(x, y) {
         return x * y;
      };
      
    4. a function expression of a function named func_name assigned to the variable multiply
      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 a var 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 the Function 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 the Function constructor should be avoided whenever possible.
      It should be noted, however, that function expressions and function declarations nested within the function generated by parsing a Function 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:

    1. scripting with DOM HTML event properties
      document.form1.colorButton.onclick = setBGColor;
      
    2. 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函数的引用,只有函数名,没有后面的括号"()",这样函数才不会被调用.

    相关链接

    文档标签和贡献者

    对本页有贡献的人: ziyunfei, byronhe, iwo, teoli
    最后编辑者: byronhe,