简体   繁体   English

如何使用纯Javascript连接getElementsByClassName的结果?

[英]How do I concatenate the results of getElementsByClassName in plain Javascript?

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 accommodate the two different pages, I wanted to compile a list of DIVs by two different class names. 一旦掌握了元素,我就可以成功地遍历和处理这些元素,但是为了容纳两个不同的页面,我想用两个不同的类名来编译DIV列表。 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?! 所以getElementsByClassName可以在两个页面上使用,但是看起来concat.apply不会迭代第一个参数,但是会迭代第二个参数吗?

So I tried using concat twice and going all out with parentheses: 因此,我尝试使用concat两次,并用括号将其全掉:

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 complement of divs. 充分利用div。

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) 作为参考,该版本位于Mac Chrome版本56.0.2924.87(64位)上

To use concat, the collections would need to be arguments that are Arrays. 要使用concat,集合必须是作为数组的参数。 Any other argument won't get flattened. 任何其他论点都不会变平。 You can use .slice() for this: 您可以.slice()使用.slice()

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: 这使用“ spread”语法创建一个新数组,其中两个集合的内容均分布在其中:

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: 或者,要使用更容易Array.from() ,可以使用Array.from()来转换集合:

traces = Array.from(document.getElementsByClassName('fullStacktrace'))
              .concat(Array.from(document.getElementsByClassName('msg'))))

Overall, I wouldn't use getElementsByClassName in the first place. 总体而言,我不会首先使用getElementsByClassName I'd use .querySelectorAll . 我会使用.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. 它使用与CSS使用的选择器相同的选择器,因此上述选择器实际上传递了一组两个选择器,每个选择器都选择具有各自类的元素。


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 combine them into a single Array. .concat()工作方式是获取作为this值及其参数的任何给定值,并将它们组合为单个Array。 However, when the this value or any of the arguments are an actual Array , it flattens its content one level into the result. 但是,当this值或任何参数是实际的Array ,它将其内容展平到结果一级。 Because an HTMLCollection isn't an Array, it's seen as just any other value to be added, and not flattened. 因为HTMLCollection不是数组,所以它被视为要添加的其他任何值,而不是被展平。

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. .apply()允许您设置被调用方法的this值,然后将其第二个参数的成员作为该方法的各个参数传播。 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() . 因此,考虑上面的例子中, HTMLCollection作为第一个参数传递给.apply()没有得到平整到结果,而是一个作为第二个参数传递给.apply()不会,因为它的.apply()做蔓延,而不是.concat() From the perspective of .concat() , the second DOM selection never existed; .concat()的角度来看,第二个DOM选择根本就不存在。 it only saw individual DOM elements that have the msg class. 它只看到具有msg类的单个DOM元素。


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. 这部分[].concat.apply(document.getElementsByClassName('fullStacktrace'))遇到与第一个示例相同的问题,但是您注意到HTMLCollection并没有最终出现在结果中。 The reason is that you actually abandoned the result of that call when you chained .apply.concat(... to the end. 原因是当您将.apply.concat(...链接到最后时,实际上放弃了该调用的结果。

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. 这只是进入.concat()方法的一种.concat()方法。 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. .concat()内部, ["bar"]将是this值,而"baz""buz"将作为单独的参数传递,因为.apply()再次将其第二个参数扩展为该方法的单独参数。

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. ...注意"foo"不在结果中。 That's because .concat() has no knowledge of it; 这是因为.concat()不了解它。 it only knows if ["bar"] , "baz" and "buz" . 它只知道["bar"]"baz""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. 第二个.concat.apply基本上删除第一个.concat.apply ,仅继续处理提供给.apply()的数据,因此第一个集合不会出现。 If you hadn't used .apply for the second call, you'd have ended up with an array with two unflattened HTMLCollection s. 如果您没有在第二次调用中使用.apply ,那么最终将得到一个数组,其中包含两个未HTMLCollection

[].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. 因为对document.getElementByClassName的调用返回的是HTMLCollection而不是Array您得到的是奇怪的结果。 What we can do to combat this is convert the HTMLCollection to an array, and then concat them, like this: 为了解决这个问题,我们可以将HTMLCollection转换为数组,然后连接它们,如下所示:

traces= [].slice.call(document.getElementsByClassName('fullStacktrace')).concat([].slice.call(document.getElementsByClassName('msg')))

If browser compatibility is a concern though, have a look at some of the other solutions from here that may help for earlier versions of IE . 但是,如果要考虑浏览器的兼容性,请查看此处提供的其他一些解决方案, 它们可能对IE的早期版本有所帮助

Explanation : 说明

Document.getElementsByClassName returns a live HTMLCollection of elements that match the provided class names. Document.getElementsByClassName返回与提供的类名匹配的元素的实时HTMLCollection 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. 因此,您最初的尝试是尝试合并两个实时HTML元素列表,而不是合并元素数组。

Suggested Solution : 建议的解决方案

Create a solid (non-live array) using Array#slice() and then you can merge the two arrays directly: 使用Array#slice()创建一个实体(非活动数组Array#slice() ,然后可以直接合并两个数组:

var fullStacktrace = [].slice.call(document.getElementsByClassName('fullStacktrace'), 0),
  msg = [].slice.call(document.getElementsByClassName('msg'), 0),

  mergedDivs = fullStacktrace.concat(msg);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM