为什么我需要将animals.includes
包装在匿名函数中才能使以下 ES6 js 代码按预期工作?
const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]
const hasPulse = nouns.some((n) => animals.includes(n))
console.log(`Heartbeat ${hasPulse ? '' : 'not '}detected!`)
如果我打开动物的包装,它会抛出一个类型错误。
// this fails with TypeError: can't convert undefined to object
const hasPulse = nouns.some(animals.includes)
这是因为Array.prototype.some()
调用回调的方式与Array.prototype.includes()
期望参数的方式不兼容。
[].includes()
的完整定义是:
[].includes(thingToSearch, startSearchFrom);
例如,如果您这样做:
[1,2,3,4,5].includes(1,3); // false
即使数组明确包含1
,它也会返回false
。这是因为您告诉它从元素3
开始搜索,因此.includes()
只查看4
和5
,找不到1
。
[].some()
的完整定义是:
[].some(function (item, index, theArray) {}, this);
因此,.some()
会将当前项的索引传递给您传递给它的回调。例如,如果您执行以下操作:
[10,20,30,40,50].some((x,y) => y == 2); // 30
它会返回30
,因为他第一次.some()
遍历它传递10
x
并0
传递给y
的数组。第二次20
传给x
,1
传给y
.第三次y
将2
,因此y == 2
为真,因此它返回true
的值,即30
。
现在您可以看到,.some()
作为第二个参数传递的内容和作为第二个参数传递的内容.includes()
期望的内容具有不同的含义。
.some()
方法将传递循环索引,而.includes()
方法期望第二个参数告诉它从哪里开始搜索。
因此,如果您这样做:
const animals = ["ape", "dog", "pig"];
const nouns = ["car", "planet", "apple", "dog"];
nouns.some(animals.includes);
.. 它会在
ape,dog,pig
中搜索car
,.. 然后它会在
dog,pig
中搜索planet
(记住索引现在是 1,所以你告诉.includes()
忽略ape
).. 然后它会在
pig
中搜索apple
(索引现在是 2,所以.includes()
忽略ape
和dog
).. 然后它会在一个空数组中搜索
dog
,当然它不会找到。
当然,您可能有一些算法想要执行此操作。但我不认为这是你期望发生的事情。
你不能接受
const hasPulse = nouns.some(Array.prototype.includes, animals);
使用thisArgs
,但不幸的是Array#includes
具有第二个参数fromIndex
,它由索引传递并破坏所需的结果。
我相信这是一个上下文问题,您应该将 include 函数绑定到名词数组,以便它按照您想要的方式工作。您拥有的代码没有按预期工作的原因是因为当您将 include 函数传递给 some 时,它不会在数组上执行(这要么未定义,要么设置为浏览器中的窗口对象)。这是工作示例
const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]
const hasPulse = nouns.some(animals.includes.bind(nouns))
console.log(`Heartbeat ${hasPulse ? '' : 'not '}detected!`)
它与Javascript方法在调用时如何"知道"它附加到哪个对象有关:它的this
引用。
includes
可能会实现如下(假设示例):
Array.prototype.includes = function includes(couldBeMember) {
for (const member of this) {
if (member === couldBeMember) {
return true;
}
}
return false;
}
animals.includes(argument)
argument
传递给Array.prototype.includes
,但也将其this
设置为在呼叫期间animals
。只是引用animals.includes
并将其传递到其他地方,与传递Array.prototype.includes
没有什么不同;如果它最终被调用(在some
内部)而不引用animals
,它不会被animals
设置为它的this
。
要创建一个"记住"其this
animals
的函数includes
,无论函数本身传递到何处,都必须将其绑定到animals
:animals.includes.bind(animals)
(或Array.prototype.includes.bind(animals)
或[].includes.bind(animals)
)。
const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]
const hasPulse = nouns.some([].includes.bind(animals))
这并不比仅使用箭头函数(或"lambda")干净得多,但希望能回答您的问题。
如果你喜欢这个想法,但想清理它,你可以创建一个独立的bind
,做这样的事情:
function bind(self, method, ...args) {
return self[method].bind(self, ...args);
}
const hasPulse = nouns.some(bind(animals, "includes"))
(至少还有一个建议来创建执行等效绑定的运算符,例如animals::includes
,但我相信这还没有最终确定,我上次检查了,所以语法可能会改变,或者最终可能不会得到支持。
some() 方法测试数组中的一个或多个元素是否通过了在提供的函数中实现的测试,并返回指示结果的布尔值。
const hasPulse = nouns.some((n) => animals.includes(n))
第二种选择不起作用。 Array.include 使用从调用函数接收的第二个参数 (fromIndex) 作为索引,并省略该函数要检查的值。