How is `each` different from `for-loop` when returning a value?

时间:2015-05-24 20:41:23

标签: javascript functional-programming find each

I have my each function which I created to emulate Underscore.js's _each() for my Javascript study.

var each = function(list, iteratee) {
    if (Array.isArray(list)) { // list is array
        for (var i = 0; i < list.length; i++) {
            iteratee(list[i], i, list);
        }
    } else if (list.constructor === Object) { // list is object
        for (var key in list) {
            iteratee(list[key], key, list);
        }
    }
};

Then I wanted to create a function find which is also available from Underscore.js. This function looks through each value in the list, returning the first one that passes a truth test (predicate), or undefined if no value passes the test. The function returns as soon as it finds an acceptable element and doesn't traverse the entire list.

Here is my version of find that I came up with.

var find = function(list, predicate) {
    each(list, function(elem){
        if (predicate(elem)) {
            return elem;
        }
    });
};

I thought it would return a value immediately after it has found a true value for an element that passes a test from an external predicate function. But instead, it's giving me an undefined.

Below code works as I expected. But why would they provide different output?

var find = function(list, predicate) {
    if (Array.isArray(list)) { // list is array
        for (var i = 0; i < list.length; i++) {
            if (predicate(list[i])) {
                return list[i];
            }
        }
    } else if (list.constructor === Object) { // list is object
        for (var key in list) {
            if (predicate(list[key])) {
                return list[key];
            }
        }
    }
};

What I don't understand is that why doesn't each works as I expected when I included it in my find function. Wouldn't they simply different in terms of their style of expression? In other word, one is functional style, and another is not?

2 个答案:

答案 0 :(得分:2)

This is caused due to the lack of return statement. Each function iterates over find but doesn't return anything. return statement in predicate returns the output to the each function where it is not expected

Example of working function:

var find = function(list, predicate) {
     var res = undefined/null/whatever;
     each(list, function(elem) {
         if (predicate(elem)) {
             res = elem;
         }
     });
     return res;
};

However this function is not efficient as it won't stop when result is found

答案 1 :(得分:1)

This has to do with how return works. Let's look at your code:

var find = function(list, predicate) {
    // you pass list and an anonymous callback to `each`
    each(list, function (elem) {
        // if this condition is true
        if (predicate(elem)) {
            // return elem
            return elem;
        }
    });
}

The problem is that return elem applies to the anonymous callback, not the find function.

If you want to be able to "break" the each loop, you can check the current state on each iteration of the for-loop within each.

// only going to write for arrays
var each = function (list, iteratee) {
    for (var i = 0; i < list.length; i++) {
        if (iteratee(list[i], i, list)) continue;
        else break;
    }
});
// then in find:
var find = function (list, predicate) {
    var ret = null
    each(list, function(elem) {
        if (predicate(elem)) {
            ret = elem;
            return false;    // return false, since we no longer wish to continue
        }
    });
    return ret;
};

A second solution is to return from within the each loop:

var each = function (list, iteratee) {
    for (var i = 0; i < list.length; i++) {
        if (iteratee(list[i], i, list)) {
            continue;
        } else {
            return list[i];
        }
    }
    // didn't find anything, so `return null`
    return null;
});
var find = function (list, predicate) {
    return each(list, function(elem) {
        // if `predicate`, return false, so the loop breaks
        return !predicate(elem);
    });
};

The only problem I have with this solution is that it distorts the meaning of each. each intuitively means "go through everything," which the second solution doesn't necessarily do.