如何在纯 Javascript 中连接 getElementsByClassName 的结果?

and*_*sel 2 javascript element concatenation

我正在尝试编写一个可在两个不同页面上使用的书签。一旦掌握了元素,我就可以成功地迭代和处理它们,但为了适应两个不同的页面,我想通过两个不同的类名编译 DIV 列表。我是这样开始的:

\n\n
traces=\n  [].concat.apply(document.getElementsByClassName(\'fullStacktrace\'),\n                  document.getElementsByClassName(\'msg\'))\n
Run Code Online (Sandbox Code Playgroud)\n\n

但在第一页上结果是这样的:

\n\n
> traces=[].concat.apply(document.getElementsByClassName(\'fullStacktrace\'),\n                         document.getElementsByClassName(\'msg\'))\n[HTMLCollection[2]]\n> traces[0]\n[div.fullStacktrace, div.fullStacktrace]\n> traces[1]\nundefined\n> traces[0][0]\n<div class=\xe2\x80\x8b"fullStacktrace" style=\xe2\x80\x8b"height:\xe2\x80\x8b auto;\xe2\x80\x8b">\xe2\x80\x8b\xe2\x80\xa6\xe2\x80\x8b</div>\xe2\x80\x8b\n
Run Code Online (Sandbox Code Playgroud)\n\n

而在另一页上

\n\n
> traces=[].concat.apply(document.getElementsByClassName(\'fullStacktrace\'),\n                         document.getElementsByClassName(\'msg\'))\n[HTMLCollection[0], div#msg_2.msg.l1,  ... div#msg_6460.msg.l1]\n> traces[0]\n[]\n> traces[1]\n<div class=\xe2\x80\x8b"msg l1" id=\xe2\x80\x8b"msg_2">\xe2\x80\x8b\xe2\x80\xa6\xe2\x80\x8b</div>\xe2\x80\x8b\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此 getElementsByClassName 适用于两个页面,但看起来 concat.apply 不会迭代其第一个参数,但会迭代其第二个参数?!

\n\n

所以我尝试使用 concat 两次并全力以赴使用括号:

\n\n
traces=(\n  (\n    [].concat.apply(document.getElementsByClassName(\'fullStacktrace\'))\n  )\n  .concat.apply(document.getElementsByClassName(\'msg\'))\n)\n
Run Code Online (Sandbox Code Playgroud)\n\n

但它变得更奇怪:第一页说:

\n\n
Array[1]\n> 0: HTMLCollection[0]\n  length: 0\n  > __proto__: HTMLCollection\nlength: 1\n> __proto__: Array[0]\n
Run Code Online (Sandbox Code Playgroud)\n\n

和另一个:

\n\n
Array[1]\n> 0: HTMLCollection[283]  // Lots of div#msg_3.msg.l1 sort of things in here\n  length: 1\n>__proto__: Array[0]\n
Run Code Online (Sandbox Code Playgroud)\n\n

获取其完整的 div 补集。

\n\n

由于两组元素仅在一个或另一个页面上,因此我可以在我的特定情况下使用条件,但上面的行为对我来说非常令人惊讶,所以我想理解它。有任何想法吗?

\n\n

作为参考,这是 Mac Chrome 版本 56.0.2924.87(64 位)

\n

小智 5

要使用 concat,集合需要是数组参数。任何其他争论都不会平息。您可以.slice()为此使用:

traces= [].concat([].slice.call(document.getElementsByClassName('fullStacktrace')),
                  [].slice.call(document.getElementsByClassName('msg')))
Run Code Online (Sandbox Code Playgroud)

现代语法会让它变得更好一些。这使用“spread”语法创建一个新数组,并将两个集合的内容分布到其中:

traces = [...document.getElementsByClassName('fullStacktrace'),
          ...document.getElementsByClassName('msg')]
Run Code Online (Sandbox Code Playgroud)

或者要使用更容易填充的东西,您可以使用Array.from()来转换集合:

traces = Array.from(document.getElementsByClassName('fullStacktrace'))
              .concat(Array.from(document.getElementsByClassName('msg'))))
Run Code Online (Sandbox Code Playgroud)

getElementsByClassName总的来说,我一开始就不会使用。我会用.querySelectorAll. 您将获得更好的浏览器支持和更强大的选择功能:

traces = document.querySelectorAll('.fullStacktrace, .msg')
Run Code Online (Sandbox Code Playgroud)

它使用与 CSS 相同的选择器,因此上面的选择器实际上传递了一组两个选择器,每个选择器选择具有各自类的元素。


详细解释

第一个例子:

我对上述问题的解释太简洁了。这是您的第一次尝试:

traces = [].concat.apply(document.getElementsByClassName('fullStacktrace'),
              document.getElementsByClassName('msg'))
Run Code Online (Sandbox Code Playgroud)

有效的方法.concat()是将给出的任何值作为其this值和参数,并将它们组合成一个数组。但是,当this值或任何参数是实际的时Array,它会将其内容平铺到结果中。因为 anHTMLCollection不是数组,所以它被视为只是要添加的任何其他值,而不是被展平。

允许.apply()您设置this被调用方法的值,然后将其第二个参数的成员作为单独的参数传播给该方法。因此,在上面的示例中,HTMLCollection作为第一个参数传递的.apply()不会被展平为结果,但作为第二个参数传递的会展平为.apply()结果,因为它.apply()正在进行传播,而不是.concat()。从 的角度来看.concat(),第二个 DOM 选择从来不存在;它只看到具有该类的单个 DOM 元素msg


第二个例子:

traces=(
  (
    [].concat.apply(document.getElementsByClassName('fullStacktrace'))
  )
  .concat.apply(document.getElementsByClassName('msg'))
)
Run Code Online (Sandbox Code Playgroud)

这有点不同。这部分[].concat.apply(document.getElementsByClassName('fullStacktrace'))遇到与第一个示例相同的问题,但您注意到HTMLCollection结果并没有出现。原因是当你链接.apply.concat(...到最后时,你实际上放弃了该调用的结果。

举一个更简单的例子。当你这样做时:

[].concat.apply(["bar"], ["baz", "buz"])
Run Code Online (Sandbox Code Playgroud)

...这[]实际上是一个浪费的数组。这只是到达该.concat()方法的一个捷径。.concat()will内部["bar"]this值,并且"baz""buz"将作为单独的参数传递,因为再次.apply()将其第二个参数作为方法的单独参数展开。

如果将简单的示例更改为以下内容,您可以更清楚地看到这一点:

["foo"].concat.apply(["bar"], ["baz", "buz"])
Run Code Online (Sandbox Code Playgroud)

...请注意,这"foo"不在结果中。那是因为.concat()对它一无所知;它只知道是否["bar"]"baz""buz"

所以当你这样做时:

[].concat.apply(collection).concat.apply(collection)
Run Code Online (Sandbox Code Playgroud)

你也做了同样的事情。第二个.concat.apply基本上放弃第一个并仅继续提供给 的数据.apply(),因此第一个集合不会出现。如果您没有使用.apply第二次调用,那么您最终会得到一个包含两个未展平的数组HTMLCollection

[].concat.apply(collection).concat(collection)
// [HTMLCollection, HTMLCollection]
Run Code Online (Sandbox Code Playgroud)