对 Array.prototype.some 使用额外的回调参数
Using extra callback parameters to Array.prototype.some
您是否有任何使用第二个和第三个参数回调到Array.prototype.some
或Array.prototype.any
的实际示例?
根据MDN:
回调由三个参数调用:元素的值、元素的索引 元素,以及正在遍历的数组对象。
我个人从未使用过它们。
我已经在Javascript函数式编程库Ramda上工作了一段时间,早期我们做出了一个有争议的决定,即不将索引和数组参数用于我们创建的其他类似函数。 这样做有充分的理由,我不需要在这里深入讨论,只是说对于某些函数,例如 map
和 filter
,我们发现这些额外的参数确实有一些偶尔的效用。 因此,我们提供了第二个函数,将它们提供给您的回调。 (例如,map.idx(yourFunc, list)
.(
但我什至从未考虑过这样做some
或every
. 我从未想过这些的实际用途。 但是现在有人建议我们将这些函数包含在支持索引的函数列表中。
所以我的问题是,你是否曾经找到过一个实际的、实时的、现实世界的回调函数来some
或every
真正需要这些参数? 如果是这样,你能描述一下吗?
我从不这样做"的答案也是有用的数据,谢谢。
在我们的代码中快速搜索:
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,3]
:- 将
1
添加到[2,3]
的每个排列中。 - 将
2
添加到[1,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
函数的index
和list
参数。所以,是的,在某些情况下,这些额外的参数确实很有帮助。
但是,它们也有问题
然而,这些多余的参数有时可能是有问题的,难以调试。这种有问题的行为最常见的例子是当map
和parseInt
一起使用时:javascript - Array#map and parseInt
alert(["1","2","3"].map(parseInt));
如您所见,它会产生意外的输出[1,NaN,NaN]
。发生这种情况的原因是map
函数调用parseInt
3个参数(item
,index
和array
(:
parseInt("1", 0, ["1","2","3"]) // 1
parseInt("2", 1, ["1","2","3"]) // NaN
parseInt("3", 2, ["1","2","3"]) // NaN
但是,parseInt
函数需要 2 个参数(string
和 radix
(:
- 第一种情况,基数
0
false
。因此,默认基数为 10,导致1
. - 第二种情况,基数是
1
。没有以 1 为基数的数字系统。因此,我们得到了NaN
. - 第三种情况,基数
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
同样,您可以创建decorateArray
和decorateIndicesArray
函数:
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中,您有两个独立的功能 map
和 map.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));
但是,我们删除了多余的论点,因为它们会引起问题。我们为什么要重新添加它们?两个原因:
- 它看起来更干净。
- 有时你有一个由其他人编写的函数,它需要这些额外的参数。
在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));
这段代码很棒,因为:
- 每个函数只执行一项工作。可以组合功能以执行更复杂的工作。
- 一切都是明确的,根据Python的禅宗,这是一件好事。
- 没有冗余。只有一个
map
功能,等等。
所以我真的希望这个答案有所帮助。
- 要求未定义JS回调参数
- Axios spread()具有未知数量的回调参数
- 节点回调参数无法断言instanceof Error
- each()-将额外的参数传递给回调参数
- AngularJS tokenWrapper-错误的回调参数
- 拉斐尔未运行回调参数
- 使用 jQuery 回调参数通过 AJAX 调用 PHP 函数
- 将函数的执行同步为 node.js 中的回调参数
- 当第一个函数完成没有回调参数时执行第二个函数
- 使用回调参数从函数中获取值
- 如何使用可选的回调参数制作 javascript 函数
- 为什么设置超时's回调参数可以接受参数
- 对 Array.prototype.some 使用额外的回调参数
- 使用Google闭包编译器记录回调参数
- JavaScript Object Literal“;这个“;关键字jQuery回调参数传递
- 在Meteor中运行的回调中未定义回调参数
- 回调参数名称
- 将jQueryajax回调参数定义为现有对象类型
- Reactjs路由器匹配回调参数总是未定义的
- Javascript自定义事件更改回调参数