函数抽象

JavaScript 里 Array 非常常用。还记得当初是因为直接字符串拼接效率好差,但是用 […].join(”) 却神奇地快而使我印象深刻。

在 JavaScript 1.6 里还引入了一些新的方法,虽然有些看上去好像没什么特别,甚至会使人疑惑为什么需要多这么个方法。我很好奇这是基于什么设计出来的。

忘记了是从哪里看来的,说一些方法是为了可以支持函数式编程而引入的。比如 forEach, filter, every, map, reduce

实际使用过后,还真是尝到甜头了。最直接的感觉就是,代码变短了(不是字符数,而是独立的语法单位数量)。比如 every

如果没有它,你需要写这些代码

var arr = [15, 20, 30];
var isbigger = true;
for (var i = 0; i < arr.length; i++) {
    if (!(arr[i] > 10))  {
        isbigger = false;
        break;
    }
}
isbigger === true

有了它,你可以这样写

var arr = [15, 20, 30];
arr.every(function(it) {
    return it > 10;
}) === true;

少定义了一个变量 isbigger, 没有了 for 循环,自然也不需要考虑什么时候 break 了。需要做的判断可以非常清晰地用 it > 10 表达出来。真是简单明快。

或许想要表达的就是这种抽象方式,将某种特定过程抽象成一个 every,使用者可以只关心所需要的逻辑。而这个逻辑是用一个函数来表示的,作为参数传入到 every 当中去。由于 JavaScript 支持函数作为参数,使得使用函数来抽象非常的方便,不需要额外构建一些模式来进行胶连。如果从这个角度看,的确很符合函数式编程的模式。

再看看 every 的实现,和第一段代码的主体是非常的相像的。

/**
 * Copyright (c) Mozilla Foundation http://www.mozilla.org/
 * This code is available under the terms of the MIT License
 */
if (!Array.prototype.every) {
    Array.prototype.every = function(fun /*, thisp*/) {
        var len = this.length >>> 0;
        if (typeof fun != "function") {
            throw new TypeError();
        }

        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in this && !fun.call(thisp, this[i], i, this)) {
                return false;
            }
        }

        return true;
    };
}

就这样一个独立功能的简单函数就可以实现关注点的分离。而且,分离之后,各自都可以很好地测试,关注点的单一,使得测试更加容易进行。功能变得简单(专注)了,少打了许多字,代码更加清晰易懂,出错的几率也相应地减少。出错少了,你会感觉是自己变聪明了(Ruby 创始人在一次演讲中说到的一个很有趣的观点)。

同样的原理,我觉得是可以很方便地应用到自己编写的代码当中去,仔细分析代码,分离不同的关注点,是实现自下而上的编程方式的重要一环。

也许看上去很简单,但是我觉得这种抽象方式作为一个很好的基础,可以帮助我们设计出更好的代码。