JavaScript函数

来源:互联网 发布:java包装类 编辑:程序博客网 时间:2024/05/16 09:46

在JavaScript里,函数即对象,程序可以随意操控它们。比如,JavaScript可以把函数赋值给变量,或者作为参数传递给其它函数。因为函数就是对象,所以可以给它们设置属性,甚至调用它们的方法。JavaScript的函数可以嵌套在其它函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。这意味着JavaScript函数构成了一个闭包,它给JavaScript带来了非常强劲的编程能力。

1、函数定义

function printprops(o) { // 函数声明语句,输出o的每个属性的名称和值    for (var p in o)        console.log(p + “: ” + o[p] + “\n”);}var square = function(x) { // 函数定义表达式,求传入参数的平方    return x * x;}data.sort(function(a, b) { return a - b; }); // 函数作为参数var tensquare = (function(x) { // 函数定义表达式定义后立即调用,传入参数为10    return x * x;}(10));function hypotenuse(a, b) { // 嵌套函数    function square(x) { return x * x; }    return Math.sqrt(square(a) + square(b));}

2、函数调用

printprops({x: 1}); // 函数调用var calculator = {    operand1: 1,    operand2: 2,    add: function() {        this.result = this.operand1 + this.operand2;    }};calculator.add(); // 方法调用var o = new Object(); // 构造函数调用f.call(o); // 间接调用,以对象o的方法来调用函数f()

3、函数参数

函数实参、形参个数不同时,省略的实参对应的形参都将是undefined,多出的实参会自动省略,但可以通过arguments取得。

当调用函数的时候传入的实参比函数声明时指定的形参个数少,剩下的形参都将设置为undefined值,如下例子:

function getPropertyNames(o, /* optional */ a) {    a = a || [];    for (var property in o) a.push(property);    return a;}var a = getPropertyNames(o); // 将o的属性存储到一个新数组中getPropertyNames(p, a); // 将p的属性追加到数组a中

当调用函数的时候传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值的引用。参数对象解决了这个问题,在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象,也包含一个length属性,这样可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。

function max(/* ... */) { // 不定实参函数,实参个数不能为零    var max = Number.NEGATIVE_INFINITY;    for (var i = 0; i < arguments.length; i++)        if (arguments[i] > max) max = arguments[i];    return max;}

除了数组元素,实参对象还定义了callee和caller属性,前者指代当前正在执行的函数,后者指代调用当前正在执行的函数的函数,这个在匿名函数中非常有用。

var factorial = function(x) {    if (x <= 1) return 1;    return x * arguments.callee(x -1);}

JavaScript方法的形参并未声明类型,在形参传入函数体之前也未做任何类型检查,可以采用语义化的单词来给函数参数命名,或者给参数补充注释,以此使代码自文档化。

当一个函数的参数过多时,记住参数顺序难免有点麻烦,可行的是把传入的实参写到一个对象中,通过名值对进行调用,如下例子将原始数组的length元素复制到目标数组:

function arraycopy(/* array */ from,                /* index */ from_start,                /* array */ to,                /* index */ to_start,                /* integer */ length) {    // ...}function easycopy(args) {    arraycopy(args.from,                args.from_start || 0// 默认值                args.to,                args.to_start || 0                args.length);}var a = [1, 2, 3, 4], b = [];easycopy({from: a, to: b, length: 4});

4、函数作为值

function square(x) { return x *x; }var s = square; // s和square指代同一个函数s(4);var o= {square: function(x) { return x * x; }}; // 函数作为对象中的一个属性值var y = o.square(16);

函数是一个特殊的对象,这就意味着函数也可以拥有属性,如下例子,计算阶乘,并将计算结果缓存至函数的属性中:

function factorial(n) {    if (isFinite(n) && n > 0 && n == Math.round(n)) {// 有限的正整数        if (!(n in factorial))            factorial[n] = n * factorial[n - 1];        return factorial[n];    }    else return NaN;}

5、函数作为命名空间

函数为什么可以作为命名空间?因为,不在任何函数内声明的变量是全局变量,在整个JavaScript程序中是可见的,而在函数中声明的变量只在整个函数内是可见的,在函数的外部是不可见的,这样在函数内定义的变量就不会污染到全局空间。

6、闭包

和其它大多数现代编程语言一样,JavaScript也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包。
从技术的角度讲,所有的JavaScript函数都是闭包,它们都是对象,它们都关联到作用域链。定义大多数函数时的作用域链在调用函数时依然有效,但这并不影响闭包,且看如下例子。

var scope = “global”;function checkscope() {    var scope = “local”;    function f() { return scope; }    return f();}checkscope() // local

修改上面的代码如下,返回结果依然是local,这就是闭包的特性。

var scope = “global”;function checkscope() {    var scope = “local”;    function f() { return scope; }    return f;}checkscope()() // local

7、函数的属性和方法

因为函数也是对象,所以它们也可以拥有属性和方法。

在函数体里,arguments.length表示传入函数的实参的个数,而函数本身的length属性则有着不同的含义,函数的length属性时只读属性,它代表函数实参的期望数量,即形参数量,可以通过arguments.callee.length获得。此外,每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做原型对象。

call()和apply()方法可以看做是某个对象的方法,通过调用方法的形式来间接调用函数,它们的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this来获得对它的引用。比如,以对象o的方法的形式调用函数f(),并传入两个参数,如下:

f.call(o, 1, 2);f.apply(o, [1, 2]);

bind()方法的主要作用就是将函数绑定到某个对象,当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数,调用新的函数将会把原始的函数f()当做o的方法来调用,传入新函数的任何实参都将传入原始函数。如下例子:

function f(y) { return this.x + y; }var o = {x: 1};var g = f.bind(o);f(2) // 3

和所有的JavaScript对象一样,函数也有toString()方法,大多数返回函数的完整源码。

定义函数还可以使用Function()构造函数,下面两种方式等效(不同的是,构造函数所创建的函数并不是使用词法作用域,函数体代码的编译总是会在顶层函数执行,也就无法捕获局部作用域):

var f = new Function(“x”, “y”, “return x * y;”);var f = function(x, y) { return x * y; }

所有的函数都是可调用对象,但并非所有的可调用对象都是函数。

8、函数式编程

使用数组方法reduce()和map()计算平均值和标准差:

var sum = function(x, y) { return x + y; }var square = function(x, y) { return x * y; }var data = [1, 1, 3, 5, 5];var mean = data.reduce(sum) / data.length; // 平均值var deviations = data.map(function(x) { return x - mean; });var stddev = Math.sqrt(deviations.map(square).reduce(sum) / (data.length - 1)); // 标准差

操作函数的函数,接收参数作为参数,如下例子判断数值的奇偶性:

function not(f) {    return function() {        var result = f.apply(this, arguments);        return !result;    }}var even = function(x) { // 判断x是否为偶数    return x % 2 === 0;}var odd = not(even); // 新函数,所做的事情完全与even()相反,也就是判断是否为奇数[1, 3, 5, 7, 9].every(odd); // true 每个元素都是奇数        

JavaScript中,还有一种函数叫做不完全函数,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数,每次函数调用叫做不完全调用,这种函数变换的特点是每次调用都返回一个函数,直到得到最终运行结果为止。

在客户端JavaScript中,代码的执行时间复杂度往往成为瓶颈,一个可行的优化方法是使用缓存技巧,其实是牺牲了算法的空间复杂度以换取更优的时间复杂度,如下memory()函数,接收一个函数作为参数,并返回带有缓存功能的版本。

function memory(f) {    var cache = {};    return function() {        var key = arguments.length +             Array.prototype.join.call(arguments, “,”);        if (key in cache) return cache(key);        else return cache[key] =             f.apply(this, arguments);    };}

写一个具有缓存功能的递归函数:

var factorial = memorize(function(n) {    return (n <= 1) ? 1 : n * factorial(n - 1);});factorial(5) // 120,对于4到1的值也有缓存
1 0