When you extract a function from an object, it loses its context, so when you call it and it accesses this
, its original value has been lost.
To fix the issue you need to use Function.prototype.bind()
to keep the this
reference pointing to the right object.
You can see the problem and how bind
works in this example:
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');
Wondering where is this
pointing when it's "lost"? It points to window
:
const obj = {
prop: 'there',
print(prefix) {
console.log(`${ prefix }: Hello ${ this.prop }.`);
}
};
const extractedPrint = obj.print;
window.prop = 'window';
extractedPrint('extractedPrint');
In your case, you need to make sure that when push
is called by forEach
its context is preserved, that is, its this
value should still be referencing the original array:
links.forEach(array.push.bind(array));
Anyway, that won't work as expected because NodeList.prototype.forEach()
calls its callback with 3 arguments: currentValue
, currentIndex
and listObj
and Array.prototype.push()
accepts multiple arguments at once, so you could do:
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>
But for each Node
or your NodeList
, you would be calling push
with 3 arguments instead of 1, ending up getting some unwanted elements on the list.
To convert a NodeList
to an Array
you could use Array.from()
instead:
console.log(Array.from(document.querySelectorAll('a')).length);
<a>1</a>
<a>2</a>
<a>3</a>
Although there are other ways to do it, like pushing all the elements one by one defining your own callback:
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>
Or the the same thing using a loop:
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>