admin管理员组文章数量:1422446
I'm trying to write a bookmarklet that works on two different pages. I can successfully iterate through and process the elements once I get hold of them, but in order to acmodate the two different pages, I wanted to pile a list of DIVs by two different class names. I started with this:
traces=
[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
but on the first page it results this:
> traces=[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
[HTMLCollection[2]]
> traces[0]
[div.fullStacktrace, div.fullStacktrace]
> traces[1]
undefined
> traces[0][0]
<div class="fullStacktrace" style="height: auto;">…</div>
whereas on the other page
> traces=[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
[HTMLCollection[0], div#msg_2.msg.l1, ... div#msg_6460.msg.l1]
> traces[0]
[]
> traces[1]
<div class="msg l1" id="msg_2">…</div>
So getElementsByClassName works for both pages, but it looks like concat.apply doesn't iterate its first argument, but does iterate its second argument?!
So I tried using concat twice and going all out with parentheses:
traces=(
(
[].concat.apply(document.getElementsByClassName('fullStacktrace'))
)
.concat.apply(document.getElementsByClassName('msg'))
)
but it gets even stranger: the first page said:
Array[1]
> 0: HTMLCollection[0]
length: 0
> __proto__: HTMLCollection
length: 1
> __proto__: Array[0]
and the other:
Array[1]
> 0: HTMLCollection[283] // Lots of div#msg_3.msg.l1 sort of things in here
length: 1
>__proto__: Array[0]
get its full plement of divs.
Since the two groups of elements are only on one or the other page, I can just use a conditional in my particular case, but the behaviour above is very surprising to me, so I would like to understand it. Any ideas?
For reference, this is on Mac Chrome Version 56.0.2924.87 (64-bit)
I'm trying to write a bookmarklet that works on two different pages. I can successfully iterate through and process the elements once I get hold of them, but in order to acmodate the two different pages, I wanted to pile a list of DIVs by two different class names. I started with this:
traces=
[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
but on the first page it results this:
> traces=[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
[HTMLCollection[2]]
> traces[0]
[div.fullStacktrace, div.fullStacktrace]
> traces[1]
undefined
> traces[0][0]
<div class="fullStacktrace" style="height: auto;">…</div>
whereas on the other page
> traces=[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
[HTMLCollection[0], div#msg_2.msg.l1, ... div#msg_6460.msg.l1]
> traces[0]
[]
> traces[1]
<div class="msg l1" id="msg_2">…</div>
So getElementsByClassName works for both pages, but it looks like concat.apply doesn't iterate its first argument, but does iterate its second argument?!
So I tried using concat twice and going all out with parentheses:
traces=(
(
[].concat.apply(document.getElementsByClassName('fullStacktrace'))
)
.concat.apply(document.getElementsByClassName('msg'))
)
but it gets even stranger: the first page said:
Array[1]
> 0: HTMLCollection[0]
length: 0
> __proto__: HTMLCollection
length: 1
> __proto__: Array[0]
and the other:
Array[1]
> 0: HTMLCollection[283] // Lots of div#msg_3.msg.l1 sort of things in here
length: 1
>__proto__: Array[0]
get its full plement of divs.
Since the two groups of elements are only on one or the other page, I can just use a conditional in my particular case, but the behaviour above is very surprising to me, so I would like to understand it. Any ideas?
For reference, this is on Mac Chrome Version 56.0.2924.87 (64-bit)
Share Improve this question asked Mar 17, 2017 at 11:52 android.weaselandroid.weasel 3,4312 gold badges34 silver badges42 bronze badges3 Answers
Reset to default 7To use concat, the collections would need to be arguments that are Arrays. Any other argument won't get flattened. You can use .slice()
for this:
traces= [].concat([].slice.call(document.getElementsByClassName('fullStacktrace')),
[].slice.call(document.getElementsByClassName('msg')))
Modern syntax would make it quite a bit nicer. This uses the "spread" syntax to create a new array with the content of both collections distributed into it:
traces = [...document.getElementsByClassName('fullStacktrace'),
...document.getElementsByClassName('msg')]
Or to use something that is more easily polyfilled, you can use Array.from()
to convert the collections:
traces = Array.from(document.getElementsByClassName('fullStacktrace'))
.concat(Array.from(document.getElementsByClassName('msg'))))
Overall, I wouldn't use getElementsByClassName
in the first place. I'd use .querySelectorAll
. You get better browser support and more powerful selection capabilities:
traces = document.querySelectorAll('.fullStacktrace, .msg')
This uses the same selectors that CSS uses, so the above selector is actually passing a group of two selectors, each of which selects the elements with its respective class.
Detailed explanation
First Example:
My explanation of the issue above was too terse. Here's your first attempt:
traces = [].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
The way .concat()
works is to take whatever values are given as its this
value and its arguments, and bine them into a single Array. However, when the this
value or any of the arguments are an actual Array
, it flattens its content one level into the result. Because an HTMLCollection
isn't an Array, it's seen as just any other value to be added, and not flattened.
The .apply()
lets you set the this
value of the method being called, and then spread the members of its second argument as individual arguments to the method. So given the above example, the HTMLCollection
passed as the first argument to .apply()
does not get flattened into the result, but the one passed as the second argument to .apply()
does, because it's .apply()
doing the spreading, not .concat()
. From the perspective of .concat()
, the second DOM selection never existed; it only saw individual DOM elements that have the msg
class.
Second Example:
traces=(
(
[].concat.apply(document.getElementsByClassName('fullStacktrace'))
)
.concat.apply(document.getElementsByClassName('msg'))
)
This is a bit different. This part [].concat.apply(document.getElementsByClassName('fullStacktrace'))
suffers from the same problem as the first example, but you noticed that the HTMLCollection
doesn't end up in the result. The reason is that you actually abandoned the result of that call when you chained .apply.concat(...
to the end.
Take a simpler example. When you do this:
[].concat.apply(["bar"], ["baz", "buz"])
...the []
is actually a wasted Array. It's just a short way to get to the .concat()
method. Inside .concat()
the ["bar"]
will be the this
value, and "baz"
and "buz"
will be passed as individual arguments, since again, .apply()
spreads out its second argument as individual arguments to the method.
You can see this more clearly if you change the simple example to this:
["foo"].concat.apply(["bar"], ["baz", "buz"])
...notice that "foo"
is not in the result. That's because .concat()
has no knowledge of it; it only knows if ["bar"]
, "baz"
and "buz"
.
So when you did this:
[].concat.apply(collection).concat.apply(collection)
You did the same thing. The second .concat.apply
basically drops the first and carries on with only the data provided to .apply()
, and so the first collection doesn't appear. If you hadn't used .apply
for the second call, you'd have ended up with an array with two unflattened HTMLCollection
s.
[].concat.apply(collection).concat(collection)
// [HTMLCollection, HTMLCollection]
Because the call to document.getElementByClassName
returns a HTMLCollection
rather than an Array
you're getting the strange results you're seeing. What we can do to bat this is convert the HTMLCollection to an array, and then concat them, like this:
traces= [].slice.call(document.getElementsByClassName('fullStacktrace')).concat([].slice.call(document.getElementsByClassName('msg')))
If browser patibility is a concern though, have a look at some of the other solutions from here that may help for earlier versions of IE.
Explanation:
Document.getElementsByClassName
returns a live HTMLCollection of elements that match the provided class names. This is an array-like object but not an array.
So your initial attempts were trying to merge two live lists of HTML elements rather than merge arrays of elements.
Suggested Solution:
Create a solid (non-live array) using Array#slice()
and then you can merge the two arrays directly:
var fullStacktrace = [].slice.call(document.getElementsByClassName('fullStacktrace'), 0),
msg = [].slice.call(document.getElementsByClassName('msg'), 0),
mergedDivs = fullStacktrace.concat(msg);
版权声明:本文标题:concatenation - How do I concatenate the results of getElementsByClassName in plain Javascript? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745340850a2654254.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论