JavaScript 之函数

 Published On May 24, 2015

In JavaScript, functions are first-class objects, i.e. they are objects and can be manipulated and passed around just like any other object. Specifically, they are Function objects.

在 JavaScript 中,函数是一等公民。比如,函数是对象,像对象一样被操作或者传递给其他变量。更进一步地说,他们是函数对象。

以上。

函数参数

从类型强弱上来分,Js 是一门非常非常弱鸡的语言。–by Rio

它不会因为你传的参数过多或过少而报运行时错误,如果传入的实参过多,那么它会用声明的那几个形参操作前几个实参,后面的全都可以用 arguments 来进行操作。。。极端情况下甚至都不用进行声明形参。如下例,计算传入参数的最大值。

function max(/**可以操作任意数量的实参**/)
{
    var max=Number.NEGATIVE_INFINITY;
    for(var i=0;i<arguments.length;i++)
    {
        if(arguments[i]>max)
            max=arguments[i];
    }
    console.log(max);
}
max(1,2,100,-1,12345);

arguments[]里的参数也可以被修改,就像可以修改形参一样。

function f(x) {
    console.log(x);
    arguments[0]=null;
    console.log(x);//参数被修改
}
f(10);

this 关键字

Js中的this关键字非常有趣。 在大多数情况下,this 的值被函数调用方式所决定,它不能再函数执行过程中被修改。函数在不同地点调用的时候也可能有所不同。

详解

this关键字没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。如果嵌套函数作为方法调用,this的值会指向调用它的对象。如果嵌套函数作为函数调用,this的值不是全局对象(非严格模式)就是undefined(严格模式)

var o = {
    m: function() {
        var self = this;// self 变量通常用来存储this,让嵌套函数使用。
        console.log(this === o);// true
        f();

        function f() {
            console.log(this === o);// false.非严格模式下:全局对象 严格模式下:undefined
            console.log(self === o);// true

            console.log(this);//chrome 里是window
        }
    }
};
o.m();

this on the object’s prototype chain

this 在原型链上的传递。

var o = {prop: 37};

function independent() {
  return this.prop;
}

o.f = independent;

console.log(o.f()); // logs 37
o.b = {g: independent, prop: 42};// o里的另一个对象。
console.log(o.b.g()); // logs 42

作为值的函数

函数可以作为参数传入。(在JS中,因为函数是一等公民,可以像变量一样扔来扔去的–by WCQ)

// 和C一样,需要传入一个对每两个元素判断大小的函数
a = ['ant', 'Bug', 'cat', 'Dog'];
a.sort();               //不区分大小写的排序
console.log(a);
a.sort(function(s, t) { //区分大小写的排序
    var a = s.toLowerCase();
    var b = t.toLowerCase();
    if (a < b) return -1;
    if (a > b) return 1;
    return 0;
});
console.log(a);

闭包

在JS函数对象的内部状态不仅包含函数的代码逻辑,还引用了当前的作用域链。如果调用函数的作用域链不同,函数当前的状态也是不同的。有可能会造成不同的输出。

var scope = "global scope";

function checkscope() {
    var scope = "local scope";

    function f() {
        return scope;
    }
    return f;
}
console.log(checkscope()());// => 'local scope'

闭包保护数据安全性

uniqueInteger 得到一个函数,每次执行uniqueInteger()可以得到一个依次增加的整数。和普通写法的优点在于,只有uniqueInteger()才能获得counter的访问权,其他函数无法修改。

// counter
var uniqueInteger = (function() {
    var counter = 0;
    return function a() {
        return counter++;
    };
}());

另一种写法是把n作为函数参数存起来,有getset来修改和访问。

// store conter with argument
function counter(n) { // n is a private variable
    return {
        get count() {
            return n++;
        },
        set count(m) {
            if (m > n)
                n = m;
            else throw Error('count can only increase');
        }
    };
}
var c = counter(1000);
console.log(c);
console.log(c.count);
console.log(c.count);
console.log(c.count = 2000);
console.log(c.count = 3000);
console.log(c.count);
console.log(c.count = 3000);

高阶函数

接收一个或多个函数为参数,可以返回一个新函数(在后面的例子中我们可以用高阶函数来打包一些新的工具函数)

一个更常见的例子,接收f()g()返回一个新函数用于计算f(g())

// calculate f(g())
function compose(f, g) {
    return function() {
        // f invoked with one argument, use call()
        // g invoked with arguments, use apply()
        return f.call(this, g.apply(this, arguments));
    };
}

不完全函数

利用高阶函数的特性,我们可以打包不完全函数。通过不完全函数的打包,我们设置一个函数f()的部分实参,得到一个新函数g(),然后在需要的时候给g()赋上参数,相当于填补了f()所缺的剩下参数。根据先行填充实参的位置(从左填起、从右填起等等),我们可以分为partialLeft()partialRight()、和partial()。当然你也可以自定义别的方式,比如隔一个填一个,隔两个填一个啦什么的。

传入参数个数不限,我仅仅用形参f指代了被打包的函数,其他全都用arguments来处理。

// 将传入的arguments对象转化为真正的数组
function array(a, n) {
    return Array.prototype.slice.call(a, n || 0);
}

function partialLeft(f) { // 外部参数在左,内部参数在右
    var args = arguments;
    return function() {
        console.log(arguments);
        var a = array(args, 1);
        console.log(a);
        a = a.concat(array(arguments));
        return f.apply(this, a);
    }
}

function partialRight(f) { // 外部参数在右,内部参数在左
    var args = arguments;
    console.log(args);
    return function() {
        var a = array(arguments);
        a = a.concat(array(args, 1));
        return f.apply(this, a);
    }
}

function partial(f) {
    var args = arguments;
    return function() {
        var a = array(args, 1);
        var i = 0,
            j = 0;
        for (; i < a.length; i++) {
            if (a[i] === undefined)
                a[i] = arguments[j++];
        }
        a = a.concat(array(arguments, j));
        return f.apply(this, a);
    }
}

var f = function(x, y, z) {
    console.log(x + ' ' + y + ' ' + z);
    return x * (y - z);
};

// f(2,3,4)
console.log(partialLeft(f, 2)(3, 4)); // 给partialLeft传入(f,2) 给返回的匿名函数传入(3,4)

//f(3,4,2)
console.log(partialRight(f, 2)(3, 4));

//f(3,2,4)
console.log(partial(f, undefined, 2)(3, 4));

函数式编程

重头戏来了,我们要用compose()partial()打包一些工具函数。然后把工具函数组合起来,实现求平均数和标准差的功能。

var map = Array.prototype.map ?
function(a, f) {
    return a.map(f);
} : function(a, f) {
    var results = [];
    for (var i = 0, len = a.length; i < len; i++) {
        if (i in a) // the in operator returns true if the specified property OR index is in the specified object
            results[i] = f.call(null, a[i], i, a);
    }
    return results;
};

var reduce = Array.prototype.reduce ?
function(a, f, initial) {
    if (arguments.length > 2) // 如果传入了初始值
        return a.reduce(f, initial);
    else {
        return a.reduce(f); // 否则没有初始值
    }
} : function(a, f, initial) {
    var i = 0,
        len = a.length,
        accumulator;
    if (arguments.length > 2) accumulator = initial;
    else {
        if (len == 0) throw TypeError();
        while (i < len) { // find the first defined index
            if (i in a) {
                accumulator = a[i++];
                break;
            } else i++;
        }
        if (i == len)
            throw TypeError();
    }

    while (i < len) {
        if (i in a)
            accumulator = f.call(accumulator, a[i], i, a);
        i++;
    }

    return accumulator;
};


var neg = partial(product, -1);
var square = partial(Math.pow, undefined, 2);
var sqrt = partial(Math.pow, undefined, .5);
var reciprocal = partial(Math.pow, undefined, -1);

var mean = product(reduce(data, sum), reciprocal(data.length));
var stddev = sqrt(
    product(reduce(map(data,compose(square,partial(sum, neg(mean)))),sum),reciprocal(sum(data.length, -1))));
    // map(data,partial(sum, neg(mean))) -3被partial()填到了 sum(x,y) 的 x 位置上。a[i] 被 map() 填到了不完全函数的缺口 y 里
console.log(mean);
console.log(stddev);

Tags: Javascript

Comments: