What is achieved by turning functions into objects

2019-07-28 07:26发布

问题:

One of the unique features of JavaScript is that 'functions are objects and can be passed around like any objects' [1].

When Mr. Eich began creating the language, why did he decide to do this? How is it advantageous compared to the usual OOP style in languages like Java? Did he have a precedent or was this a completely unique idea at the time?

回答1:

Certainly he did not invent it, first-class functions have existed since the '60s.

The power it gives you is to write so-called higher-class functions - functions that take other functions as arguments. This is the corner-stone of functional programming. For example, Array.map is a function that takes another function as input, allowing powerful constructions.

Object-Oriented programming was developed later, and concentrates more on coupling/separating data and behaviour. This is often at the expense of easily reasoning about the code (both in the colloquial and mathematical senses). Many OO languages (Java, C#) are now adding in these elements of functional programming (i.e. lambdas).

Consider the complexity of implementing Array.map, Array.reduce, Array.filter in a language like Java. Every time you want to use it, you have to create an instance of a special anonymous inner class just for the purpose of implementing the function required for the algorithm to call. In JavaScript functions are just objects like everything else, so you can just pass one in, leading to much more concise and natural syntax.



回答2:

@OrangeDog has it on the nose. I'm writing this answer just to point out that you can actually implement all of the "classic" imperative flow control with functions, arrays, and recursion only (assuming true == 1 and false == 0):

function ifElse(cond, trueFunc, falseFunc) {
    var paths = [falseFunc, trueFunc]
    return paths[cond()]();
}

function whileTrue(cond, action) {
    return ifElse(cond, whileTrue(cond, action), function() {});
}

function forLoop(initial, cond, increment, action) {
    initial();
    return whileTrue(cond, function() {
        var actionValue = action();
        increment();
        return actionValue;
    }
}

function first(array) { // or ``car``
    return array[0];
}

function rest(array) { // or ``cdr``
    return array.slice(1);
}

function each(array, action) {
    action(first(array));
    each(rest(array), action);
}

And so on. This is a result of Javascript's Lisp/Scheme roots (it's prototype inheritance model was taken from Self, and the syntax from C/Java), and it can be quite powerful, because you can easily define new flow control mechanisms without any changes to the language itself, such as:

function categorizer(array, categorizer) {
    var categories = {};
    array.forEach(function(value, index, array) {
        var category = categorizer(value, index, array);
        ifElse(function() {
            return categories[category] instanceof Array ? 1 : 0;
        }, function() {
            categories[category].push(value);
        }, function() {
            categories[category] = [value];
        });
    });
    return categories;
}

This categorizer can now take a function that will define how to split an array into a set of labeled arrays in an object, that can be used elsewhere, such as:

categorize(['foo', 'bar', 'baz'], function(value) {
    return value.charAt(0);
});

// Produces the following:
{
    f: ['foo'],
    b: ['bar', 'baz']
}