1

I am currently using the Chrome console to do some debugging for a Greasemonkey script.

From the console I run var opp = document.querySelectorAll('a[class="F-reset"]'); and it works as expected.

But if I try to remove the first element with opp.splice(0,1) I get the following error

Uncaught TypeError: opp.splice is not a function
    at <anonymous>:2:5
    at Object.InjectedScript._evaluateOn (<anonymous>:905:140)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:838:34)
    at Object.InjectedScript.evaluate (<anonymous>:694:21)

If I try to run opp[0].indexOf("a"), i get the same thing.

How would I fix this?

Bijan
  • 7,737
  • 18
  • 89
  • 149

4 Answers4

4

Yes, this is because the result of querySelectorAll is a node list, not an array. You can apply the slice method of Array to a node list, however:

Array.prototype.slice.call(op, 0, 1);

This works more or less as expected, because a NodeList "quacks" in just the way slice expects, i.e. it contains elements indexed sequentially. However, you should be wary of using this in general; it is much safer to simply iterate over the NodeList.

Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • 1
    _NodeList_ is guaranteed to be _Array-like_ and _slice_ is [_intentionally generic_](http://es5.github.io/#x15.4.4.10) to work with _Array-likes_. There is nothing _"unsafe"_ here. **splice** though, probably won't work and is not safe on a _NodeList_. – Paul S. Sep 04 '15 at 00:49
  • @PaulS. Yes, *in the case of `Array` and `slice`*, this is perfectly safe. However, in general, unless you understand the underlying implementation well, you should avoid applying functions from the prototype of one object to another; if you're lucky you'll get an error, if you're unlucky you'll silently get unexpected behavior. – Asad Saeeduddin Sep 04 '15 at 00:51
  • Slightly off topic, isn't opp.splice(0,1) supposed to _remove_ the first element? Your call is only returning the first element – Bijan Sep 04 '15 at 01:01
  • @Bijan Yes, but I would say removing the first element from a `querySelectorAll` result is a useless operation. Just get the portion you're interested in. Perhaps if OP was splicing some portion in the middle it would make sense, but you can still do that with two `slice`s. – Asad Saeeduddin Sep 04 '15 at 01:03
1

querySelectorAll returns a NodeList. This is similar to an array (it has a .length property and you can index it with []), but it's not actually an array, and doesn't have most of the array methods. If you want to use array methods on an array-like object, you have to call the method explicitly:

Array.prototype.splice.call(opp, 0, 1);

or:

[].splice.call(opp, 0, 1);

However, another difference between arrays and NodeLists is that you can't modify NodeList in place, which .splice tries to do; you can only read them like arrays. You should just use .slice() to extract the parts you want. Or convert the NodeList to an array first, and then operate on that. See

Fastest way to convert JavaScript NodeList to Array?

Community
  • 1
  • 1
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • _querySelectorAll_ actually returns a _non-live NodeList_ (You are right that some _NodeLists_ can be _live_ though, just not here) – Paul S. Sep 04 '15 at 00:52
  • Also, [_splice_ is _intentionally generic_](http://es5.github.io/#x15.4.4.12), but AFAIK you can't perform an assignment on a _NodeList_ so it will fail, `TypeError: Cannot set property length of # which has only a getter`. Solution? `slice` then `splice` – Paul S. Sep 04 '15 at 00:57
  • 1
    Thanks for the clarifications. I've updated my answer to remove the issue about live NodeLists and just talk about their non-modifiability. – Barmar Sep 04 '15 at 01:01
0

querySelector/All returns a NodeList not an array, so those functions are not available.

You can use call to use those array methods though

[].splice.call(opp,0,1);

The first argument is the execution context that the function will use, all other arguments are the arguments that will be passed to the function

Function call reference

Patrick Evans
  • 41,991
  • 6
  • 74
  • 87
0

First,

  • splice is a method of Arrays, inherited through Array.prototype, although it is intentionally generic so can be called on other Arraylike objects
  • querySelectorAll returns a non-live NodeList, this is not an Array and does not share any inheritance with Array, meaning you can't simply access Array methods through it
  • A function can be invoked with a custom this via call or apply
  • splice needs to be able to assign on it's this, which will fail for a NodeList as you will get the following TypeError: Cannot set property length of #<NodeList> which has only a getter
  • Other intentionally generic Array methods which only read from this will work on a NodeList, e.g. slice, map, indexOf, forEach, filter, some, every, etc..

Now we are in a position to do something,

  1. Convert the NodeList to an Array and store this reference, i.e. with Array.prototype.slice
  2. Perform your splice on this object instead

So,

var opp = document.querySelectorAll('a[class="F-reset"]'); // NodeList
oop = Array.prototype.slice.call(oop); // Array
// ...
oop.splice(0, 1);
Paul S.
  • 64,864
  • 9
  • 122
  • 138