如何将 getElementsByClassName 的结果串联成纯 Javascript?
How do I concatenate the results of getElementsByClassName in plain Javascript?
我正在尝试编写一个可在两个不同页面上运行的小书签。一旦我掌握了这些元素,我就可以成功地遍历和处理这些元素,但是为了适应这两个不同的页面,我想用两个不同的 class 名称编译一个 DIV 列表。我从这个开始:
traces=
[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
但是在第一页上结果是这样的:
> 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>
而在另一页上
> 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>
所以 getElementsByClassName 对两个页面都有效,但看起来 concat.apply 没有迭代它的第一个参数,但是迭代它的第二个参数?!
所以我尝试使用 concat 两次并用括号全力以赴:
traces=(
(
[].concat.apply(document.getElementsByClassName('fullStacktrace'))
)
.concat.apply(document.getElementsByClassName('msg'))
)
但它变得更加奇怪:第一页说:
Array[1]
> 0: HTMLCollection[0]
length: 0
> __proto__: HTMLCollection
length: 1
> __proto__: Array[0]
另一个:
Array[1]
> 0: HTMLCollection[283] // Lots of div#msg_3.msg.l1 sort of things in here
length: 1
>__proto__: Array[0]
获取完整的 div。
由于两组元素只在一个或另一个页面上,我可以在我的特定情况下使用条件,但上面的行为让我非常惊讶,所以我想了解它。有什么想法吗?
供参考,这是 Mac Chrome 版本 56.0.2924.87(64 位)
要使用 concat,集合需要是数组的参数。任何其他论点都不会被压平。您可以为此使用 .slice()
:
traces= [].concat([].slice.call(document.getElementsByClassName('fullStacktrace')),
[].slice.call(document.getElementsByClassName('msg')))
现代语法会使它更好一些。这使用 "spread" 语法创建一个新数组,其中包含分布在其中的两个集合的内容:
traces = [...document.getElementsByClassName('fullStacktrace'),
...document.getElementsByClassName('msg')]
或者要使用更容易填充的东西,您可以使用 Array.from()
转换集合:
traces = Array.from(document.getElementsByClassName('fullStacktrace'))
.concat(Array.from(document.getElementsByClassName('msg'))))
总的来说,我一开始不会使用 getElementsByClassName
。我会使用 .querySelectorAll
。您将获得更好的浏览器支持和更强大的选择功能:
traces = document.querySelectorAll('.fullStacktrace, .msg')
这使用了与CSS相同的选择器,所以上面的选择器实际上传递了一组两个选择器,每个选择器选择具有各自class的元素。
详细解释
第一个例子:
我对上述问题的解释过于简洁。这是您的第一次尝试:
traces = [].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
.concat()
的工作方式是将给定的任何值作为其 this
值及其参数,并将它们组合成一个数组。但是,当 this
值或任何参数是实际的 Array
时,它会将其内容平展到结果中一个级别。因为 HTMLCollection
不是数组,所以它被视为要添加的任何其他值,而不是展平。
.apply()
允许您设置被调用方法的 this
值,然后将其第二个参数的成员作为单独的参数传播到该方法。因此,在上面的示例中,作为第一个参数传递给 .apply()
的 HTMLCollection
不会被压平到结果中,但是作为第二个参数传递给 .apply()
的 HTMLCollection
会压平,因为它是 .apply()
进行传播,而不是 .concat()
。从.concat()
的角度来看,第二个DOM选择根本不存在;它只看到具有 msg
class.
的单个 DOM 元素
第二个例子:
traces=(
(
[].concat.apply(document.getElementsByClassName('fullStacktrace'))
)
.concat.apply(document.getElementsByClassName('msg'))
)
这有点不同。这部分 [].concat.apply(document.getElementsByClassName('fullStacktrace'))
遇到与第一个示例相同的问题,但您注意到 HTMLCollection
并没有出现在结果中。原因是当您将 .apply.concat(...
链接到末尾时,您实际上放弃了该调用的结果。
举一个更简单的例子。当您这样做时:
[].concat.apply(["bar"], ["baz", "buz"])
...[]
实际上是一个浪费的数组。这只是到达 .concat()
方法的捷径。在 .concat()
中,["bar"]
将是 this
值,并且 "baz"
和 "buz"
将作为单独的参数传递,因为 .apply()
传播将其第二个参数作为方法的单独参数输出。
如果把简单的例子改成这样,你会看得更清楚:
["foo"].concat.apply(["bar"], ["baz", "buz"])
...请注意 "foo"
不在结果中。那是因为 .concat()
不知道它;它只知道 ["bar"]
、"baz"
和 "buz"
.
所以当你这样做时:
[].concat.apply(collection).concat.apply(collection)
你也做了同样的事情。第二个 .concat.apply
基本上放弃了第一个并仅继续提供给 .apply()
的数据,因此第一个集合不会出现。如果您没有在第二次调用中使用 .apply
,您最终会得到一个包含两个未展平 HTMLCollection
的数组。
[].concat.apply(collection).concat(collection)
// [HTMLCollection, HTMLCollection]
因为对 document.getElementByClassName
returns 的调用是 HTMLCollection
而不是 Array
,所以您得到的结果很奇怪。我们可以做的是将 HTMLCollection 转换为数组,然后连接它们,如下所示:
traces= [].slice.call(document.getElementsByClassName('fullStacktrace')).concat([].slice.call(document.getElementsByClassName('msg')))
如果浏览器兼容性是一个问题,请查看 here that may help for earlier versions of IE 中的其他一些解决方案。
解释:
Document.getElementsByClassName
returns 实时HTML集合 匹配提供的class 名称的元素。这是一个类似数组的对象,但不是数组。
所以您最初的尝试是尝试合并两个 HTML 元素的实时列表,而不是合并元素数组。
建议的解决方案:
使用Array#slice()
创建实体(非实时数组),然后您可以直接合并两个数组:
var fullStacktrace = [].slice.call(document.getElementsByClassName('fullStacktrace'), 0),
msg = [].slice.call(document.getElementsByClassName('msg'), 0),
mergedDivs = fullStacktrace.concat(msg);
我正在尝试编写一个可在两个不同页面上运行的小书签。一旦我掌握了这些元素,我就可以成功地遍历和处理这些元素,但是为了适应这两个不同的页面,我想用两个不同的 class 名称编译一个 DIV 列表。我从这个开始:
traces=
[].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
但是在第一页上结果是这样的:
> 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>
而在另一页上
> 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>
所以 getElementsByClassName 对两个页面都有效,但看起来 concat.apply 没有迭代它的第一个参数,但是迭代它的第二个参数?!
所以我尝试使用 concat 两次并用括号全力以赴:
traces=(
(
[].concat.apply(document.getElementsByClassName('fullStacktrace'))
)
.concat.apply(document.getElementsByClassName('msg'))
)
但它变得更加奇怪:第一页说:
Array[1]
> 0: HTMLCollection[0]
length: 0
> __proto__: HTMLCollection
length: 1
> __proto__: Array[0]
另一个:
Array[1]
> 0: HTMLCollection[283] // Lots of div#msg_3.msg.l1 sort of things in here
length: 1
>__proto__: Array[0]
获取完整的 div。
由于两组元素只在一个或另一个页面上,我可以在我的特定情况下使用条件,但上面的行为让我非常惊讶,所以我想了解它。有什么想法吗?
供参考,这是 Mac Chrome 版本 56.0.2924.87(64 位)
要使用 concat,集合需要是数组的参数。任何其他论点都不会被压平。您可以为此使用 .slice()
:
traces= [].concat([].slice.call(document.getElementsByClassName('fullStacktrace')),
[].slice.call(document.getElementsByClassName('msg')))
现代语法会使它更好一些。这使用 "spread" 语法创建一个新数组,其中包含分布在其中的两个集合的内容:
traces = [...document.getElementsByClassName('fullStacktrace'),
...document.getElementsByClassName('msg')]
或者要使用更容易填充的东西,您可以使用 Array.from()
转换集合:
traces = Array.from(document.getElementsByClassName('fullStacktrace'))
.concat(Array.from(document.getElementsByClassName('msg'))))
总的来说,我一开始不会使用 getElementsByClassName
。我会使用 .querySelectorAll
。您将获得更好的浏览器支持和更强大的选择功能:
traces = document.querySelectorAll('.fullStacktrace, .msg')
这使用了与CSS相同的选择器,所以上面的选择器实际上传递了一组两个选择器,每个选择器选择具有各自class的元素。
详细解释
第一个例子:
我对上述问题的解释过于简洁。这是您的第一次尝试:
traces = [].concat.apply(document.getElementsByClassName('fullStacktrace'),
document.getElementsByClassName('msg'))
.concat()
的工作方式是将给定的任何值作为其 this
值及其参数,并将它们组合成一个数组。但是,当 this
值或任何参数是实际的 Array
时,它会将其内容平展到结果中一个级别。因为 HTMLCollection
不是数组,所以它被视为要添加的任何其他值,而不是展平。
.apply()
允许您设置被调用方法的 this
值,然后将其第二个参数的成员作为单独的参数传播到该方法。因此,在上面的示例中,作为第一个参数传递给 .apply()
的 HTMLCollection
不会被压平到结果中,但是作为第二个参数传递给 .apply()
的 HTMLCollection
会压平,因为它是 .apply()
进行传播,而不是 .concat()
。从.concat()
的角度来看,第二个DOM选择根本不存在;它只看到具有 msg
class.
第二个例子:
traces=(
(
[].concat.apply(document.getElementsByClassName('fullStacktrace'))
)
.concat.apply(document.getElementsByClassName('msg'))
)
这有点不同。这部分 [].concat.apply(document.getElementsByClassName('fullStacktrace'))
遇到与第一个示例相同的问题,但您注意到 HTMLCollection
并没有出现在结果中。原因是当您将 .apply.concat(...
链接到末尾时,您实际上放弃了该调用的结果。
举一个更简单的例子。当您这样做时:
[].concat.apply(["bar"], ["baz", "buz"])
...[]
实际上是一个浪费的数组。这只是到达 .concat()
方法的捷径。在 .concat()
中,["bar"]
将是 this
值,并且 "baz"
和 "buz"
将作为单独的参数传递,因为 .apply()
传播将其第二个参数作为方法的单独参数输出。
如果把简单的例子改成这样,你会看得更清楚:
["foo"].concat.apply(["bar"], ["baz", "buz"])
...请注意 "foo"
不在结果中。那是因为 .concat()
不知道它;它只知道 ["bar"]
、"baz"
和 "buz"
.
所以当你这样做时:
[].concat.apply(collection).concat.apply(collection)
你也做了同样的事情。第二个 .concat.apply
基本上放弃了第一个并仅继续提供给 .apply()
的数据,因此第一个集合不会出现。如果您没有在第二次调用中使用 .apply
,您最终会得到一个包含两个未展平 HTMLCollection
的数组。
[].concat.apply(collection).concat(collection)
// [HTMLCollection, HTMLCollection]
因为对 document.getElementByClassName
returns 的调用是 HTMLCollection
而不是 Array
,所以您得到的结果很奇怪。我们可以做的是将 HTMLCollection 转换为数组,然后连接它们,如下所示:
traces= [].slice.call(document.getElementsByClassName('fullStacktrace')).concat([].slice.call(document.getElementsByClassName('msg')))
如果浏览器兼容性是一个问题,请查看 here that may help for earlier versions of IE 中的其他一些解决方案。
解释:
Document.getElementsByClassName
returns 实时HTML集合 匹配提供的class 名称的元素。这是一个类似数组的对象,但不是数组。
所以您最初的尝试是尝试合并两个 HTML 元素的实时列表,而不是合并元素数组。
建议的解决方案:
使用Array#slice()
创建实体(非实时数组),然后您可以直接合并两个数组:
var fullStacktrace = [].slice.call(document.getElementsByClassName('fullStacktrace'), 0),
msg = [].slice.call(document.getElementsByClassName('msg'), 0),
mergedDivs = fullStacktrace.concat(msg);