为什么不能将`push`作为foreach函数传递?

时间:2018-09-11 20:08:34

标签: javascript foreach

var array = [];
document.querySelectorAll("a").forEach(array.push); //Uncaught TypeError: Cannot convert undefined or null to object

这为什么会失败?这个错误是什么意思?将数组方法作为函数传递似乎是完全合理的,我不了解/看不到什么?

6 个答案:

答案 0 :(得分:4)

array.push丢失了上下文(其this),因此您需要传递一个捕获上下文的函数。

但是,即使您想要的工作成功了-您仍然无法获得所需的结果,因为NodeList:forEach将3个参数传递给了回调函数,因此您将用元素,索引来填充array以及节点列表。

所以解决办法是

var array = [];
document.querySelectorAll("a").forEach(e => array.push(e));

答案 1 :(得分:2)

您可以重新绑定此上下文

var arr = []; document.querySelectorAll("a").forEach(arr.push.bind(arr));
console.log(arr);
<a href="#foo1">one</a>
<a href="#foo2">two</a>
<a href="#foo3">three</a>

这里最大的问题是,push()被赋予多个参数时,会将所有这些参数添加到数组中。

因此push(1,2,3)将向数组添加1、2和3。因此,上面的代码将具有3个链接,但是它将为数组添加9个条目,因为forEach具有3个参数element, index, array。因此,您将需要使用一个函数来执行此操作。或者只是使用Array.from()创建数组。

var arr = []; 
document.querySelectorAll('a').forEach(el => arr.push(el))
console.log(arr);

var arr2 = Array.from(document.querySelectorAll('a'))
console.log(arr2);
<a href="#foo1">one</a>
<a href="#foo2">two</a>
<a href="#foo3">three</a>

答案 2 :(得分:1)

最初的问题不是传递array.push,问题是您正在遍历NodeLists,并且此结构不是数组,可以通过以下方式使用Array.from:

const array =Array.from(document.querySelectorAll("a"));

答案 3 :(得分:1)

从对象中提取函数时,该函数会丢失其上下文,因此,当您调用该函数并访问this时,其原始值将丢失。

要解决此问题,您需要使用Function.prototype.bind()来保持this引用指向正确的对象。

在此示例中,您可以看到问题以及bind的工作方式:

const obj = {
  prop: 'there',

  print(prefix) {
    console.log(`${ prefix }: Hello ${ this.prop }.`);
  }
};

obj.print('obj.print');

// Context lost:

const extractedPrint = obj.print;

extractedPrint('extractedPrint');

// Context preserved to the original object:

const bindedPrint = obj.print.bind(obj);

bindedPrint('bindedPrint');

// Context replaced:

const alsoBindedPrint = obj.print.bind({ prop: 'bro' });

alsoBindedPrint('alsoBindedPrint');

想知道this在“丢失”时指向何处?它指向window

const obj = {
  prop: 'there',

  print(prefix) {
    console.log(`${ prefix }: Hello ${ this.prop }.`);
  }
};

const extractedPrint = obj.print;

window.prop = 'window';

extractedPrint('extractedPrint');

在您的情况下,您需要确保push调用forEach时,其上下文得以保留,也就是说,其this的值仍应引用原始数组:

links.forEach(array.push.bind(array));

无论如何,由于NodeList.prototype.forEach()使用3个参数调用其回调,因此无法正常工作:currentValuecurrentIndexlistObjArray.prototype.push()接受多个一次争论,所以你可以做:

const array = [];
const links = document.querySelectorAll('a');

links.forEach(array.push.bind(array));

console.log(array.length);
<a>1</a>
<a>2</a>
<a>3</a>

但是对于每个Node或您的NodeList,您将使用3个参数(而不是1个)来调用push,最终会在列表中得到一些不需要的元素。

要将NodeList转换为Array,可以改用Array.from()

console.log(Array.from(document.querySelectorAll('a')).length);
<a>1</a>
<a>2</a>
<a>3</a>

尽管还有其他方法,例如定义定义自己的回调的所有元素一一推送:

const links = document.querySelectorAll('a');
const arr = [];

// Note we only define a single argument, so we ignore the other 2:
links.forEach((link) => {
  arr.push(link);
});

console.log(arr);
<a>1</a>
<a>2</a>
<a>3</a>

还是使用循环来做同样的事情:

const links = document.querySelectorAll('a');
const arr = [];

for (const link of links) {
  arr.push(link);
}

// Also valid:

// for (let i = 0; i < links.length; ++i) {
//   arr.push(links[i]);
// }

console.log(arr);

// Note this won't work, as NodeList has some other iterable
// properties apart from the indexes:

const arr2 = [];

for(const i in links) {
  arr2.push(links[i])
}

console.log(arr2);
<a>1</a>
<a>2</a>
<a>3</a>

答案 4 :(得分:0)

我不是100%地确定您要做什么,但是我猜测您想将querySelectorAll返回的元素推入“数组”。在这种情况下,您不能仅将函数push传递给forEach,就必须以每个元素作为参数调用push on array,就像这样:

document.querySelectorAll("a").forEach(item => array.push(item));

答案 5 :(得分:0)

您要尝试执行什么示例? 也许您缺少一些参数?

尝试一下:

document.querySelectorAll("a").forEach(function(item) {
    array.push(item);
});