对 Array.prototype.some 使用额外的回调参数

Using extra callback parameters to Array.prototype.some

本文关键字:回调 参数 Array prototype some      更新时间:2023-09-26

您是否有任何使用第二个和第三个参数回调到Array.prototype.someArray.prototype.any的实际示例?

根据MDN:

回调由三个参数调用:元素的值、元素的索引 元素,以及正在遍历的数组对象。

我个人从未使用过它们。

我已经在Javascript函数式编程库Ramda上工作了一段时间,早期我们做出了一个有争议的决定,即不将索引和数组参数用于我们创建的其他类似函数。 这样做有充分的理由,我不需要在这里深入讨论,只是说对于某些函数,例如 mapfilter ,我们发现这些额外的参数确实有一些偶尔的效用。 因此,我们提供了第二个函数,将它们提供给您的回调。 (例如,map.idx(yourFunc, list) .(

但我什至从未考虑过这样做someevery. 我从未想过这些的实际用途。 但是现在有人建议我们将这些函数包含在支持索引的函数列表中。

所以我的问题是,你是否曾经找到过一个实际的、实时的、现实世界的回调函数来someevery真正需要这些参数? 如果是这样,你能描述一下吗?

"不,

我从不这样做"的答案也是有用的数据,谢谢。

在我们的代码中快速搜索:

function isAscending(array) {
    return array.every(function (e, idx, arr) {
        return (idx === 0) ? true : arr[idx-1] <= e;
    });
}

我可以想象如下代码来检查数组是否无重复:

….every(function(v, i, arr) {
    return arr.indexOf(v, i+1) == -1;
})

其中是一个复杂的表达式,因此您实际上必须使用 arr 参数 - 如果您正确地分解出将数组作为参数的自己的函数中的功能,这将不再是问题。

第二个参数有时可能很有用,但我支持你的立场,即它很少使用。

是的,它们很有帮助

这些额外的参数实际上确实派上用场,但并不经常。

在最近的过去,我写了一个函数来查找元素列表的所有排列:

permute :: [a] -> [[a]]

例如permute [1,2,3]将是:

[ [1,2,3]
, [1,3,2]
, [2,1,3]
, [2,3,1]
, [3,1,2]
, [3,2,1]
]

这个函数的实现非常简单:

  1. 如果输入[]则返回[[]] 。这是边缘情况。
  2. 如果输入[1,2,3]
    1. 1添加到[2,3]的每个排列中。
    2. 2添加到[1,3]的每个排列中。
    3. 3添加到[1,2]的每个排列中。

当然,该函数是递归的。在 JavaScript 中,我按如下方式实现它:

var permute = (function () {
    return permute;
    function permute(list) {
        if (list.length === 0) return [[]];     // edge case
        else return list.reduce(permutate, []); // list of permutations
                                                // is initially empty
    }
    function permutate(permutations, item, index, list) {
        var before = list.slice(0, index); // all the items before "item"
        var after = list.slice(index + 1); // all the items after "item"
        var rest = before.concat(after);   // all the items beside "item"
        var perms = permute(rest);         // permutations of rest
        // add item to the beginning of each permutation
        // the second argument of "map" is the "context"
        // (i.e. the "this" parameter of the callback)
        var newPerms = perms.map(concat, [item]);
        return permutations.concat(newPerms); // update the list of permutations
    }
    function concat(list) {
        return this.concat(list);
    }
}());

如您所见,我使用了permutate函数的indexlist参数。所以,是的,在某些情况下,这些额外的参数确实很有帮助。

但是,它们也有问题

然而,这些多余的参数有时可能是有问题的,难以调试。这种有问题的行为最常见的例子是当mapparseInt一起使用时:javascript - Array#map and parseInt

alert(["1","2","3"].map(parseInt));

如您所见,它会产生意外的输出[1,NaN,NaN]。发生这种情况的原因是map函数调用parseInt 3个参数(itemindexarray(:

parseInt("1", 0, ["1","2","3"]) // 1
parseInt("2", 1, ["1","2","3"]) // NaN
parseInt("3", 2, ["1","2","3"]) // NaN

但是,parseInt函数需要 2 个参数(stringradix(:

  1. 第一种情况,基数0 false。因此,默认基数为 10,导致 1 .
  2. 第二种情况,基数是1。没有以 1 为基数的数字系统。因此,我们得到了NaN.
  3. 第三种情况,基数2有效。但是,基数 2 中没有3。因此我们得到NaN.

如您所见,多余的参数会导致许多难以调试的问题。

但是,还有另一种选择

因此,这些额外的参数很有帮助,但它们可能会导致很多问题。幸运的是,这个问题有一个简单的解决方案。

在Haskell中,如果你想map一个值列表和每个值的索引,那么你使用如下方式:

map f (zip list [0..])
list                   :: [Foo]
[0..]                  :: [Int]
zip list [0..]         :: [(Foo, Int)]
f                      :: (Foo, Int) -> Bar
map f (zip list [0..]) :: [Bar]

你可以在 JavaScript 中做同样的事情,如下所示:

function Maybe() {}
var Nothing = new Maybe;
Just.prototype = new Maybe;
function Just(a) {
    this.fromJust = a;
}
function iterator(f, xs) {
    var index = 0, length = xs.length;
    return function () {
        if (index < length) {
            var x = xs[index];
            var a = f(x, index++, xs);
            return new Just(a);
        } else return Nothing;
    };
}

我们使用不同的map函数:

function map(f, a) {
    var b = [];
    if (typeof a === "function") { // iterator
        for (var x = a(); x !== Nothing; x = a()) {
            var y = f(x.fromJust);
            b.push(y);
        }
    } else {                       // array
        for (var i = 0, l = a.length; i < l; i++) {
            var y = f(a[i]);
            b.push(y);
        }
    }
    return x;
}

最后:

function decorateIndices(array) {
    return iterator(function (item, index, array) {
        return [item, index];
    }, array);
}
var xs = [1,2,3];
var ys = map(function (a) {
    var item = a[0];
    var index = a[1];
    return item + index;
}, decorateIndices(xs));
alert(ys); // 1,3,5

同样,您可以创建decorateArraydecorateIndicesArray函数:

function decorateArray(array) {
    return iterator(function (item, index, array) {
        return [item, array];
    }, array);
}
function decorateIndicesArray(array) {
    return iterator(function (item, index, array) {
        return [item, index, array];
    }, array);
}

目前在Ramda中,您有两个独立的功能 mapmap.idx 。上述解决方案允许您将map.idx替换为idx,以便:

var idx = decorateIndices;
var arr = decorateArray;
var idxArr = decorateIndicesArray;
map.idx(f, list) === map(f, idx(list))

这将允许您摆脱一大堆.idx函数和变体。

咖喱还是不咖喱

还有一个小问题需要解决。这看起来很丑:

var ys = map(function (a) {
    var item = a[0];
    var index = a[1];
    return item + index;
}, decorateIndices(xs));

能够这样写会更好:

var ys = map(function (item, index) {
    return item + index;
}, decorateIndices(xs));

但是,我们删除了多余的论点,因为它们会引起问题。我们为什么要重新添加它们?两个原因:

  1. 它看起来更干净。
  2. 有时你有一个由其他人编写的函数,它需要这些额外的参数。

在Haskell中,您可以使用uncurry函数来解决此问题:

map (uncurry f) (zip list [0..])
list                             :: [Foo]
[0..]                            :: [Int]
zip list [0..]                   :: [(Foo, Int)]
f                                :: Foo -> Int -> Bar
uncurry                          :: (a -> b -> c) -> (a, b) -> c
uncurry f                        :: (Foo, Int) -> Bar
map (uncurry f) (zip list [0..]) :: [Bar]

在 JavaScript 中,uncurry 函数只是简单地apply。它的实现方式如下:

function uncurry(f, context) {
    if (arguments.length < 2) context = null;
    return function (args) {
        return f.apply(context, args);
    };
}

使用uncurry我们可以将上面的例子写成:

var ys = map(uncurry(function (item, index) {
    return item + index;
}), decorateIndices(xs));

这段代码很棒,因为:

  1. 每个函数只执行一项工作。可以组合功能以执行更复杂的工作。
  2. 一切都是明确的,根据Python的禅宗,这是一件好事。
  3. 没有冗余。只有一个map功能,等等。

所以我真的希望这个答案有所帮助。