译文:Javascript-Functions

个人理解+google翻译+有道翻译。如有错误,请指正。原文来自MDN:Functions

Functions是javascript基本构建模块之一。每一个function是一个javascript程序--一组执行任务或计算值的语句。要使用一个function,必须在调用该function范围某处先作定义。

functions 定义(函数定义)

一个函数的定义(也成为函数声明)由关键字function设定,然后是:

  • 函数的名字
  • 传递给函数的的参数列表,这些参数用括号括起来,使用逗号分隔。
  • 定义函数的javascript语句,使用花括弧({})括起来。

例如下面的代码中,定义了一个名为square的简单函数:

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

square函数接收一个参数,名为number。该函数由一条语句组成,即返回参数number的乘积。

return number * number;

初始参数(如一个数字)通过值传递给函数,传递给函数的值如果被被函数修改了,这种情况下值的修改不反映到全局环境或函数调用中(this change is not reflected globally or in the calling function.)。

如果传递一个对象(即非原始值,如Array或用户自定义对象)参数,函数修改对象的属性,这种修改对于函数外部是可见的,看下面的用例演示:

function myFunc(theObject) {
    theObject.make = "Toyota";
}

var mycar = {make: "Honda", model: "Accord", year: 1998},
    x,
    y;
    x = mycar.make;     // x 取值 "Honda"
    myFunc(mycar);
    y = mycar.make;     // y 取值 "Toyota"
                        // (make 属性已经被函数修改)

注意:赋值一个新的对象给参数对函数外部没有任何影响,因为这种情况改变了参数的值,而不是改变了对象的属性值:

function myFunc(theObject) {
    theObject = {make: "Ford", model: "Focus", year: 2006};
}

var mycar = {make: "Honda", model: "Accord", year: 1998},
    x,
    y;
x = mycar.make;     // x 取值 "Honda"
myFunc(mycar);
y = mycar.make;     // y 一直取值 "Honda"

在第一个例子中,对象mycar传给函数myFunc,并改变了对象mycar。

对于原文有不同看法:

原文:In the second case, the function did not alter the object that was passed; instead, it created a new local variable that happens to have the same name as the global object passed in,

翻译:在第二个例子中,函数没有改变传过来的对象;函数创建了一个新的局部变量,只不过这个新的局部变量与传入的全局对象名称相同。

此处函数并不是创建了一个新的局部变量,而是,theObject作为函数的参数,本身就是个局部变量,在函数体内的theObject = {make: "Ford", model: "Focus", year: 2006};

其实是该参数指向了一个新的对象而非创建了一个新的局部变量。传入的对象在函数内被“改变”。

所以对传入的全局对象没有任何影响。

以上的函数声明是语法上地函数声明,函数也可以通过函数表达式创建。函数可以是匿名的,不必有一个名称。例如,函数square能被定义为:

var square = function(number) {return number * number};
var x = square(4) //x 取值 16

此外,函数名可以提供一个函数表达式,并且可以用于在函数内部指向本身,或在调试器中堆栈跟踪过程确定函数:

var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};
console.log(factorial(3));//6 函数的递归

当函数作做为参数传递给另一个函数时,函数表达式非常方便。下面的例子中展示了一个名为map的函数,调用它使用一个匿名函数作为其第一个参数:

function map(f,a) {
    var result = [],
    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,8,125,1000

在JavaScript中,函数可以根据条件来定义。例如,下面的函数定义中只有当num等于0时定义myFunc:

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

除了描述性的定义函数,也可以在程序运行时使用 Function constructor(函数构造)和一个字符串创建函数,与eval()很相似;例如

var setBGColor = new Function("document.bgColor = 'antiquewhite'");//将一个函数分配给变量setBGColor。该函数设置当前文档的背景颜色

一个方法是一个函数,这个函数方法也可以作为对象的属性。关于objects和methods更多信息参考Working with Objects

调用函数

定义一个函数而不运行它。则定义的函数只是为该函数命名和指定该函数被调用的时候去做什么事情。调用函数时实际是通过指定的参数执行指定的动作。例如,定义了一个函数square,你会按下面的方式调用:

square(5);

前面的语句是以5为参数调用square函数。该函数执行内部语句返回值25。

函数被调用时肯在一个作用域内,但函数的声明可以在函数调用下面,例如:

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

函数的作用域由其在声明时所在的范围,或是整个程序如果在顶级范围声明。注意这种运行方式只在上例的语法下定义函数(即fucnction funName(){})。下面的代码不会运行。

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

函数的参数不只局限于字符串和数字类型。也可以传一个对象到函数。例子show_prop函数(定义在Working with Objects)接收一个对象作为参数。

函数可以是递归的,也就是说,它可以调用本身。例如,这里是一个函数,递归计算阶乘:

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

然后可以通过下面的代码计算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

还有其他的方法来调用函数.经常有函数需要动态调用或变化函数的参数数量,或在运行时的函数上下文中设置一个指定的确定对象。证明functions是它们自己,也是对象,对象反过来含有方法(参见Function object)。【It turns out that functions are, themselves, objects, and these objects in turn have methods (see the <i>Function object</i>). 】 apply()能够实现这一目标。

函数作用域

定义在函数内的变量不能被函数外部任何地方访问,因为只是定义在函数范围内。然而在已定义的函数内,一个函数可以使用所有的定义的变量和函数。也就是说,全局作用域内定义的函数可以使用全局作用域内所有定义的变量。在另一个函数内定义的函数也可以使用其在父函数内定义的和其它父函数可访问的所有变量。

// 下面的变量定义在全局作用域内
var num1 = 20,
    num2 = 3,
    name = "Chamahk";
// 这个函数定义在全局作用域内
function multiply() {
    return num1 * num2;
}
multiply(); // Returns 60
// 一个内嵌函数示例
function getScore () {
    var num1 = 2,
          num2 = 3;              
    function add() {
        return name + " scored " + (num1 + num2);
      }              
      return add();
}
getScore(); // Returns "Chamahk scored 5"

闭包

闭包是Javascript非常强大的功能之一。JavaScript允许函数嵌套,此外,授予内部函数访问所有其外包函数内定义的变量和函数(其它变量和函数需要能被父函数访问。)但外包函数不能访问其内部函数定义的变量和函数。这样使内部函数的变量得到了安全。此外,因为内部函数可以在外包函数的作用域内访问,所以当内部函数管理周期超过外包函数生存期时,在外包函数内定义的变量和函数将会比外包函数存在时间更长。当内部函数在外包函数外部任意范围以某种方式变为有效代码时就创建了一个闭包。

var pet = function(name) {         // 外包函数定义name变量
    var getName = function() {
        return name;               // 内部函数访问外包函数的name变量
    }
return getName;                    // 返回内部函数,从而暴露到外部作用域
},
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

在上面的代码中,外包函数的变量名可以被内部函数访问,而且没有其他方式访问内部变量除非通过内部函数访问。内部函数的变量作为内部函数的安全存储。它们保持特殊的“持久状态”,但为内部函数的运行提供安全的数据(They hold "persistent", yet secure, data for the inner functions to work with)。这些内部函数甚至可以不分配一个变量或拥有一个名称。

var getCode = (function(){
var secureCode = "0]Eal(eh&2";    // 一处代码不希望被外部修改...          
    return function () {
        return secureCode;
    };
})();

getCode();    // 返回保密的代码

但是要提防使用闭包时的一些陷阱。如果一个闭包函数定义了一个与外部作用域变量同名的变量,将没有任何办法再次指向外部作用域的同名变量。

var createPet = function(name) {    // 外包函数定义了一个名为name的变量
    return {
        setName: function(name) {    // 闭包函数也定义了一个名为name的变量
            name = name;             // ??? 我们如何访问外包函数定义的name变量 ???
        }
    }
}

闭包内的this变量非常神奇及不可捉摸。他们必须小心使用,因为this的指向完全取决于函数的调用,而非取决在哪定义。关于闭包的精品文章在这可以找到:closures(闭包)

使用 arguments 参数对象

函数的参数保存在一个类似数组的对象内。可以像下面的代码在函数中寻址传递给函数的参数。

arguments[i];

其中i是参数的起始值为0索引序号。所以传递给函数的第一个参数会是argument[0]。参数的总数用的arguments.length表示。

使用arguments参数对象时,可以使用比函数声明接收的参数数量更多的参数来调用函数。通常在你事先不知道有多少参数将被传递给函数时是很有用处的。可以使用arguments.length来确定实际传递给函数的参数的个数,然后使用arguments 参数对象来访问每个参数。

function myConcat(separator) {
    var result = "", // 初始化列表
        i;
       // 遍历参数
    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变量类似于数组,不是真正的数组。arguments变量类似数组是因为它编号索引和length属性,此外,也不具备数组处理方法。更多信息参看Javascript参考Function object

预定义函数

javascript有几个一级预定义函数:

以下各节将介绍这些函数,更多详细信息参考 JavaScript Reference

eval 函数

eval函数不引用特定对象来运行计算一行javascript字符串类型的代码,其语法如下:

eval(expr);

expr为运行的字符串。

如果该字符串代表一个表达式,eval运行计算该表达式。如果该参数代表一行或多行JavaScript语句中,eval执行语句。eval代码作用域与调用eval代码作用域相同。不要用eval运行一个计算表达式;javascript自动运行计算表达式。

isFinite 函数

isFinite函数确定参数是否为一个有限数字。语法如下:

isFinite(number);

number为数字类型。

如果参数为NaN,正无穷大或负无穷大,此方法返回false,否则返回true。

下面的代码检查客户端的输入,确定它是否是一个有限数字。

if(isFinite(ClientInput)){
        /* 执行指定步骤 */
}

isNaN 函数

isNaN函数判断参数是否是“NaN”(非数字值),语法如下:

isNaN(testValue);

testValue为需要判断的值。

parseFloat函数和parseInt函数运行一个非数字值时,返回NaN。如果传入一个“NaN”(非数字),isNaN返回true,否则false。

下面的代码确定floatValue是否是一个数字,然后相应地调用对应程序。

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

parseInt函数 和 parseFloat函数

两个解析(转换)函数,parseInt和parseFloat,当参数为字符串时返回一个数字值。

parseFloat语法:

parseFloat(str);

其中parseFloat解析它的参数字符串str,并试图返回一个浮点数.如果遇到一个字符不是一个符号(+或 - ),数字(0-9),小数点或指数,则在此处返回值,并忽略该字符及其后的所有字符。如果第一个字符不能被转换为数字,则返回“NaN”(非数字)。

parseInt语法

parseInt(str [, radix]);

parseInt函数解析转换第一个字符串参数str,并尝试返回一个以指定的radix参数为基数的整数。radix为可选参数。理由radix为10则转换为十进制数,为8时转换八进制数,16时转换为十六进制等等。对高于十的进制数,使用字母表中的字母来表示大于9的数。例如十六进制数使用A-F。

如果parseInt函数遇到一个不是指定基数的数字字符,则忽略该字符和后续字符,返回解析到该点的整数值。如果第一个字符不能转换为指定基数的数字,返回NaN。parseInt函数截取字符串转为整数。

Number 和 String 函数

Number 和 String 函数可以把一个对象转为数字类型或字符串类型。语法如下:

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

objRef作为对象引用。Number作为对象的valeOf()方法;String作为对象的toString()方法。

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

var D = new Date(430054663215),
    x;
x = String(D); // x equals "Thu Aug 18 04:37:43 GMT-0700 (太平洋夏令时) 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以上已废弃)

escape和unescape 函数对非ASCII字符不能正常运行,已被弃用。在JavaScript1.5和更高版本,使用encodeURI,decodeURI,encodeURIComponent和decodeURIComponent。

escape和unescape函数可以对字符串进行编码和解码。escape函数返回参数在ISO拉丁字符集十六进制编码。unescape函数返回指定的十六进制编码值的ASCII字符串。

语法如下:

escape(string);
unescape(string);

它们主要用于服务器端JavaScript对URL键值对进行编码和解码。