mozilla
您的搜索结果

    函数

    函数是搭建JavaScript语言大厦的基本构件之一。一个函数本身就是一段JavaScript程序——包含用于执行某一任务或计算的一系列语句。要使用某一个函数,你必需在想要调用这个函数的执行域的某处定义它。

    定义函数

    一个函数的定义(也称为函数的声明)由一系列的函数关键词组成, 依次为:

    • 函数的名称。
    • 包围在括号()中,并由逗号区隔的一个函数引数(译注:实际参数)列表。
    • 包围在花括号{}中,用于定义函数功能的一些JavaScript语句。

    例如,以下的代码定义了一个名为square的简单函数(译注:其实并非看上去那么“简单”):

    function square(number) {
      return number * number;
    }
    

    函数square使用了一个参数,叫作number。这个函数只有一个语句,它说明该函数会将函数的数(即number)自乘后返回。函数的return语句确定了函数的返回值。

    return number * number;
    

    原始参数(比如一个具体的数字)被作为传递给函数;值被传递给函数,但是如果被调用函数改变了这个参数的值,这样的改变不会影响到全局或调用的函数。

    如果你传递一个对象(即一个非实际值,例如矩阵或用户自定义的其它对象)作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的,如下面的例子所示:

    function myFunc(theObject) {
      theObject.make = "Toyota";
    }
    
    var mycar = {make: "Honda", model: "Accord", year: 1998},
        x,
        y;
    
    x = mycar.make;     // x gets the value "Honda"
    
    myFunc(mycar);
    y = mycar.make;     // y gets the value "Toyota"
                        // (the make property was changed by the function)
    

    请注意,重新给参数分配一个对象,并会对函数的外部有任何影响,因为这样只是改变了参数的值,而不是改变了对象的一个属性值:

    function myFunc(theObject) {
      theObject = {make: "Ford", model: "Focus", year: 2006};
    }
    
    var mycar = {make: "Honda", model: "Accord", year: 1998},
        x,
        y;
    
    x = mycar.make;     // x gets the value "Honda"
    
    myFunc(mycar);
    y = mycar.make;     // y still gets the value "Honda" 
    

    在第一段例子中,对象mycar被传递给了函数myFunc,进而函数改变了它。 第二段例子里,函数并没有改变传递来的对象;相反,它生成了一个新的恰好和传递的全局对象同名的局部变量,因此对传递来的全局对象没有任何影响。

    在JavaScript语言中,一个函数可以在满足一定条件后才被定义。例如,下面的函数定义只有在num为0时,才定义函数myFunc

    if (num == 0){
      function myFunc(theObject) {
        theObject.make = "Toyota"
      }
    }
    

    如果num不为0,函数不会被定义,因而任何执行它的尝试将会失败。

    此处要注意ECMAScript标准并不允许函数像上例那样出现在上下文里,仅仅允许直接在其他函数的内部或者在程序的顶级,因此上例在ECMAScript里是非法的。

    警告:JavaScript语言的不同实现处理类似非标准构造的方式也是不同的,因而最好的方式是在写可迁移代码的时候就避免它。否则,你的代码可能会在一些浏览器下工作正常而对另一些出错。

    除了以上讨论的定义函数的方法之外,你仍然可以用函数生成器从字符串实时生成函数,就像eval()

    方法是函数本身作为对象的属性。请参考有关对象和方法的用对象编程一文。

    当然上述函数定义都用的是语法语句,函数也同样可以由函数表达式产生。这样的函数可以是匿名的;它不必有名称。例如,上面提到的函数square也可这样来定义:

    var square = function(number) {return number * number};
    

    必要时,函数名称可与函数表达式同时存在,并且可以用于在函数内部代指其本身,或者在调试器堆栈跟踪中鉴别该函数:

    var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};
    
    print(factorial(3));
    

    函数表达式在将函数作为一个引数传递给其它函数时十分方便。下面的例子演示了一个叫map的函数如何被定义,而后调用一个匿名函数作为其第一个参数:

    function map(f,a) {
      var result = [], // Create a new Array
          i;
      for (i = 0; i != a.length; i++)
        result[i] = f(a[i]);
      return result;
    }
    

    下面的代码:

    map(function(x) {return x * x * x}, [0, 1, 2, 5, 10]);
    

    返回 [0, 1, 8, 125, 1000].

    调用函数

    定义一个函数并不会自动的执行它。定义了函数仅仅是赋予函数以名称并明确函数被调用时该做些什么。调用函数才会以给定的参数真正执行这些动作。例如,一旦你定义了函数square,你可以如下这样调用它:

    square(5);
    

    上述语句以引数(译注:即实际参数)5来调用函数。函数执行完它的语句会返回值25。

    函数一定要处于调用它们的域中,但是函数的声明可以在它们的调用语句之后,如下例:

    print(square(5));
    /* ... */
    function square(n){return n*n} 
    

    函数的域是指函数被声明时的所在函数,或者函数在顶级被声明时指整个程序。注意只有使用如上的语法形式(即如function funcName(){})才可以。而形如下面的代码是无效的。

    print(square(5));
    square = function (n) {
      return n * n;
    }
    

    函数的引数并不局限于字符或数字。你也可以将整个对象传递给函数。函数show_props(其定义参见用对象编程有关章节)就是一个将对象作为引数的例子。

    函数可以被递归;就是说函数可以调用其本身。例如,下面这个函数计算递归的阶乘值:

    function factorial(n){
      if ((n == 0) || (n == 1))
        return 1;
      else
        return (n * factorial(n - 1));
    }
    

    你可以在其后计算下面5个阶乘值:

    var a, b, c, d, e;
    a = factorial(1); // a gets the value 1
    b = factorial(2); // b gets the value 2
    c = factorial(3); // c gets the value 6
    d = factorial(4); // d gets the value 24
    e = factorial(5); // e gets the value 120
    

    还有其它的方式来调用函数。常见的一些情形是需要函数被动态的调用,或者函数的引数数量是变化的,或者在调用函数的上下文中,函数的引数需要被实时设置为一个特定的对象。这将把函数本身转变为对象,且这些对象在转换中有不同的方法(参考函数对象一文)。作为此中情形之一,apply()方法可以被用于这种目的。(译者:此小节不明硬译!?)

    函数的域

    在函数内定义的变量不能从函数之外的任何地方取得,因为变量仅仅在该函数的域的内部有定义。相反对应的,一个函数可以取得在它的域中定义的任何变量和子函数。换言之,定义在全局域中的函数可以取得所有定义在全局域中的变量。而定义在一个函数内部的子函数可以取得定义在其父函数内的,或已经由其父函数取得的任何变量。

    // The following variables are defined in the global scope
    var num1 = 20,
        num2 = 3,
        name = "Chamahk";
    
    // This function is defined in the global scope
    function multiply() {
      return num1 * num2;
    }
    
    multiply(); // Returns 60
    
    // A nested function example
    function getScore () {
      var num1 = 2,
          num2 = 3;
      
      function add() {
        return name + " scored " + (num1 + num2);
      }
      
      return add();
    }
    
    getScore(); // Returns "Chamahk scored 5"
    

    作用域和函数堆栈

    递归

    一个函数可以指向并调用自身。有三种方法可以达到这个目的:

    1. 通过使用函数名
    2. 使用arguments.callee
    3. 使用作用域下的一个变量名来指向函数

    例如,思考一下如下的函数定义:

    var foo = function bar() {
       // statements go here
    };

    在这个函数体内,以下的语句是等价的:

    1. bar()
    2. arguments.callee()
    3. foo()

    调用自身的函数我们称之为递归函数。在某种意义上说,递归近似于循环。两者都重复执行相同的代码,并且两者都需要一个终止条件以避免无限循环或者无限递归。例如以下的循环:

    var x = 0;
    while (x < 10) { // "x < 10" is the loop condition
       // do stuff
       x++;
    }

    可以被转化成一个递归函数和对其的调用:

    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);

    不过,有些算法并不能简单的用循环来实现。例如,获取树结构中所有的节点时,递归来实现要容易得多:

    function walkTree(node) {
      if (node == null) // 
        return;
      // do something with node
      for (var i = 0; i < node.childNodes.length; i++) {
        walkTree(node.childNodes[i]);
      }
    }

    跟循环函数相比,这里每个递归调用都产生了更多的递归。

    将递归算法转换为非递归算法是可能的,不过逻辑上通常会更加复杂,而且需要使用堆栈。事实上,递归函数就使用了堆栈:函数堆栈。

    这种类似堆栈的行为可以在下例中看到:

    function foo(i) {
      if (i < 0)
        return;
      console.log('begin:' + i);
      foo(i - 1);
      console.log('end:' + i);
    }
    foo(3);
    
    // Output:
    
    // begin:3
    // begin:2
    // begin:1
    // begin:0
    // end:0
    // end:1
    // end:2
    // end:3

    嵌套函数和闭包

    你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数是容器(外部)函数的私有成员。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)。

    既然嵌套函数是一个闭包,就意味着一个嵌套函数可以继承容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。

    可以总结如下:

    • 内部函数只可以在外部函数中访问
    • 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量

    下面的例子展示了嵌套函数:

    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); // Think of it like: give me a function that adds 3 to whatever you give it
    result = fn_inside(5); // returns 8
    
    result1 = outside(3)(5); // returns 8

    变量状态保存

    注意到上例中inside被返回时x是怎么被保留下来的。一个闭包必须保存它可见作用域中所有的参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用都实际上重新创建了一遍这个闭包。只有当inside的返回值没有再被引用时,内存才会被释放。

    fn_inside = outside(3);

    多层嵌套函数

    函数可以被多层嵌套。例如,函数A可以包含函数B,函数B可以再包含函数C。B和C都形成了闭包,所以B可以访问A,C可以访问B和A。因此,闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为域链。(稍后会详细解释)

    思考一下下面的例子:

    function A(x) {
      function B(y) {
        function C(z) {
          console.log(x + y + z);
        }
        C(3);
      }
      B(2);
    }
    A(1); // logs 6 (1 + 2 + 3)

    在这个例子里面,C可以访问B的y和A的x。这是因为:

    1. B形成了一个包含A的闭包,B可以访问A的参数和变量
    2. C形成了一个包含B的闭包
    3. B包含A,所以C也包含A,C可以访问B和A的参数和变量。换言之,C用这个顺序链接了B和A的作用域

    反过来却不是这样。A不能访问C,因为A看不到B中的参数和变量,C是B中的一个变量,所以C是B私有的。

    命名冲突

    当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。

    看以下的例子:

    function outside() {
      var x = 10;
      function inside(x) {
        return x;
      }
      return inside;
    }
    result = outside()(20); // returns 20 instead of 10

    命名冲突发生在return x上,inside的参数x和外部变量x发生了冲突。这里的作用链域是{insideoutside, 全局对象}。因此inside具有最高优先权,返回了传入的20而不是外部函数的变量值10。

    闭包

    (译注:请参考JavaScript指南的闭包一文)

    闭包是JavaScript中最强大的特性之一。JavaScript允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。而且,当内部函数生存周期大于外部函数时,由于内部函数可以访问外部函数的作用域,定义在外部函数的变量和函数的生存周期就会大于外部函数本身。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。

    var pet = function(name) {          // The outer function defines a variable called "name"
          var getName = function() {
            return name;                // The inner function has access to the "name" variable of the outer function
          }
    
          return getName;               // Return the inner function, thereby exposing it to outer scopes
        },
        myPet = pet("Vivie");
        
    myPet();                            // Returns "Vivie"
    

    实际上可能会比上面的代码复杂的多。在下面这种情形中,返回了一个包含可以操作外部函数的内部变量方法的对象。

    var createPet = function(name) {
      var sex;
      
      return {
        setName: function(newName) {
          name = newName;
        },
        
        getName: function() {
          return name;
        },
        
        getSex: function() {
          return sex;
        },
        
        setSex: function(newSex) {
          if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
            sex = newSex;
          }
        }
      }
    }
    
    var pet = createPet("Vivie");
    pet.getName();                  // Vivie
    
    pet.setName("Oliver");
    pet.setSex("male");
    pet.getSex();                   // male
    pet.getName();                  // Oliver
    

    在上面的代码中,外部函数的name变量对内嵌函数来说是可取得的,而除了通过内嵌函数本身,没有其它任何方法可以取得内嵌的变量。内嵌函数的内嵌变量就像内嵌函数的保险柜。它们会为内嵌函数保留“稳定”——而又安全——的数据参与运行。而这些内嵌函数甚至不会被分配给一个变量,或者不必一定要有名字。

    var getCode = (function(){
      var secureCode = "0]Eal(eh&2";    // A code we do not want outsiders to be able to modify...
      
      return function () {
        return secureCode;
      };
    })();
    
    getCode();    // Returns the secret code
    

    尽管有上述优点,使用闭包时仍然要小心避免一些陷阱。如果一个闭包的函数用外部函数的变量名定义了同样的变量,那在外部函数域将再也无法指向该变量。

    var createPet = function(name) {  // Outer function defines a variable called "name"
      return {
        setName: function(name) {    // Enclosed function also defines a variable called "name"
          name = name;               // ??? How do we access the "name" defined by the outer function ???
        }
      }
    }
    

    闭包中的神奇变量this是非常诡异的。使用它必须十分的小心,因为this什么完全取决于函数在何处被调用,而不是在何处被定义。一篇绝妙而详尽的关于闭包的文章可以在这里找到。

    使用引数对象

    函数的引数(译注:即实际参数)会被保存在一个类似数组的对象中。在函数内,你可以按如下方式找出传入的引数:

    arguments[i]
    

    其中i是引数的序数编号,以0开始。所以第一个传来的引数会是arguments[0]。引数的全部数量由arguments.length表示。

    使用引数对象,你可以用比它正式声明会接受的更多引数来调用函数。这在你事先不知道会需要将多少引数传递给函数时十分有用。你可以用arguments.length来决定传递给函数的引数的数量,然后用arguments对象来取得每个引数。

    例如,设想有一个用来连接字符串的函数。唯一事先确定的引数,是在连接后的字符串中用来分隔各个连接部分的字符(译注:比如例子里的分号“;”)。该函数定义如下:

    function myConcat(separator) {
       var result = "", // initialize list
           i;
       // iterate through arguments
       for (i = 1; i < arguments.length; i++) {
          result += arguments[i] + separator;
       }
       return result;
    }
    

    你可以给这个函数传递任何数量的引数,它会将各个引数连接成一个字符串“表”:

    // returns "red, orange, blue, "
    myConcat(", ", "red", "orange", "blue");
    
    // returns "elephant; giraffe; lion; cheetah; "
    myConcat("; ", "elephant", "giraffe", "lion", "cheetah");
    
    // returns "sage. basil. oregano. pepper. parsley. "
    myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");
    
    注意: arguments  变量只是 ”类数组对象“,并不是一个数组。称其为类数组对象是说它有一个索引编号和Length属性。尽管如此,它并不拥有全部的Array对象的操作方法。

    更多信息请阅读JavaScript大参考里的函数对象一文。

    预定义的函数

    JavaScript语言有好些个顶级的预定义函数:

    下面的几节介绍了这些函数。此类函数的更详细信息请阅读JavaScript大参考

    eval函数

    eval函数对一串JavaScript代码字符求值,并且不限于特定的对象。eval的语法是这样的:

    eval(expr);
    

    这里的expr是一个被求值的字符串。

    若该字符串是一个表达式,eval函数就对这个表达式求值。若这个引数表示了一个或多个JavaScript语句,eval函数就执行这些语句。eval所执行的代码的域,取决于调用代码所在的域。不要用eval对算数表达式求值;JavaScript语言会自动计算数学表达式。

    isFinite函数

    isFinite函数对引数求值,判断其是否为有限的数。isFinite的语法是:

    isFinite(number);
    

    其中number是需要求值的数。

    若引数是非数字(译注:术语为NaN)、正无穷或负无穷,本方法会返回false,否则为true。(译者:此处隐含的意思即函数是一种方法。此为行文精采之处,但奈何不明言之?)

    下面的代码检查用户的的输入,判断其是否为有限的数字。

    if(isFinite(ClientInput)){
       /* take specific steps */
    }
    

    isNaN函数

    isNaN函数对引数求值,判断其是否为“NaN”(即“不是一个数字”的缩写)。isNaN的语法是:

    isNaN(testValue);
    

    这儿的testValue是你需要对其求值的引数。

    当求值后知道结果不是数字时,parseFloatparseInt函数返回"NaN"。而isNaN函数在传递来的引数为"NaN"时返回真,否则为非真。(译者:有点儿绕,不过当然还是对的)

    下面的代码对引数floatValue求值,判断其是否为数字,然后执行相应的程序:

    var floatValue = parseFloat(toFloat);
    
    if (isNaN(floatValue)) {
       notFloat();
    } else {
       isFloat();
    }
    

    parseInt和parseFloat函数

    parseIntparseFloat这两个“解析”函数,当引数为一个给定字符串时,将返回一个数字值。

    parseFloat的语法是:

    parseFloat(str);
    

    此处函数parseFloat解析它的引数,即字符串str,并尝试返回一个浮点数。若它遇到了不是正负号(+或-)、数目字(0-9)、小数点或者一个指数的字符,它将返回当前位置的值,并忽略该字符和所有其后的字符。若第一个字符就无法被转换为数字,它将返回“NaN”(即“不是一个数字”的缩写)。

    parseInt的语法是:

    parseInt(str [, radix]);
    

    parseInt将解析它的第一个引数,字符串str,并尝试返回一个指定基数radix(即进位制)表示的整数,此处的基数radix(即进位制)由第二个可选的引数指定。例如,基数为十时会转换为十进制数,八时为八进制,十六则为十六进制,以此类推。若基数大于十,字母将用来表示大于十的数目位。例如,对十六进制数(基数为16),字母A到F会被用作数目字。

    parseInt遇到了一个不能以给定基数表示的字符,将忽略它和其后的所有字符,并返回一个解析到当前位置为止的整数值。若第一个字符就无法被转换为以给定基数表示的数字,它将返回“NaN”。即parseInt函数会将字符串截取为整数值。

    Number和String函数

    NumberString函数让你能够将一个对象转化为数字或字符串。此类函数的语法如下:(译者:此处隐含的意思即函数可对“对象”进行操作。此亦为行文精采之处,不明言之而潜移默化地使用“对象可以用作函数的引数”的概念,而汉译的“对象”一词似略不达意,且将此处深意破坏殆尽。)

    var objRef;
    objRef = Number(objRef);
    objRef = String(objRef);
    

    上面的objRef是一个对象引用。Number函数使用对象的alueOf()方法;而String函数使用对象的toString()方法。(译者:此处不明!?)

    下例把对象Date转换为可读的的字符串。

    var D = new Date(430054663215),
        x;
    x = String(D); // x equals "Thu Aug 18 04:37:43 GMT-0700 (Pacific Daylight Time) 1983"
    

    下例把String对象转换为Number对象。

    var str = "12",
        num;
    num = Number(str);
    

    你可以自己试一下。使用DOM的write()方法和JavaScript语言的typeof运算符。

    var str = "12",
        num;
    document.write(typeof str);
    document.write("<br/>");
    num = Number(str);
    document.write(typeof num);
    

    escape和unescape函数(译注:JavaScript 1.5以上已废止)

    escapeunescape函数在非ASCII编码字符下工作不正常,已经被废弃。在JavaScript 1.5和之后的版本中,请使用encodeURIdecodeURIencodeURIComponentdecodeURIComponent

    escapeunescape函数让你能编码和解码字符串。escape函数返回引数的ISO拉丁字符集的十六进制编码。而unescape函数返回该十六进制编码值相应的ASCII编码字符串值。

    这些函数语法分别是:

    escape(string);
    unescape(string);
    

    以上函数主要在服务器端的JavaScript脚本中,用来编码和解码URLs中的名字/值对。

    文档标签和贡献者

    标签: 
    Contributors to this page: teoli, ziyunfei, iwo, sunorry, smartkid, snowsolf, lvjs, SamuraiMe, Cjavaer, duckisaac
    最后编辑者: SamuraiMe,