Почему я не могу передать `push` как функцию foreach? - PullRequest
0 голосов
/ 11 сентября 2018
var array = [];
document.querySelectorAll("a").forEach(array.push); //Uncaught TypeError: Cannot convert undefined or null to object

Почему это не удается?Что означает эта ошибка?Кажется совершенно разумным передать метод массива как функцию, что я не понимаю / не вижу?

Ответы [ 6 ]

0 голосов
/ 11 сентября 2018

Вы можете привязать этот контекст

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>
0 голосов
/ 11 сентября 2018

Что вы пытаетесь подтолкнуть в своем примере?Может быть, вам не хватает некоторых параметров?

Попробуйте:

document.querySelectorAll("a").forEach(function(item) {
    array.push(item);
});
0 голосов
/ 11 сентября 2018

array.push теряет контекст (его this), поэтому вам нужно передать функцию с захваченным контекстом.

Но, даже если бы то, что вы хотели, сработало - вы все равно не получили бы желаемый результат, поскольку NodeList:forEach передает 3 аргумента в функцию обратного вызова, поэтому вы бы заполнили array элементами, индексами и списком узлы.

Так что решение было бы сделать

var array = [];
document.querySelectorAll("a").forEach(e => array.push(e));
0 голосов
/ 11 сентября 2018

Когда вы извлекаете функцию из объекта, она теряет свой контекст, поэтому, когда вы вызываете ее и получаете доступ к 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 аргументами: currentValue, currentIndex и listObj и Array.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 вы будете вызывать push с 3 аргументами вместо 1, что приведет к получению некоторых нежелательных элементовв списке.

Для преобразования 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>
0 голосов
/ 11 сентября 2018

Я не уверен на 100%, что вы пытаетесь сделать, но я собираюсь догадаться, что вы хотите поместить элементы, возвращаемые querySelectorAll, в массив. В этом случае вы не можете просто передать функцию push в forEach, вы должны вызвать массив push для каждого элемента в качестве аргумента, например:

document.querySelectorAll("a").forEach(item => array.push(item));
0 голосов
/ 11 сентября 2018

Проблема в первом случае заключается не в передаче array.push, проблема в том, что вы перебираете NodeLists, и эта структура не является массивом, вы можете использовать Array.from следующим образом:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...