querySelectorAll上的forEach在最近的Microsoft浏览器中不起作用



我正在制作一个关于产品(颜色等)的选择脚本,它适用于除InternetExplorer(11)&边缘

我把每个参数的选择放在一个数组中,并用array.forEach()方法对它们应用一个函数。

颜色参数示例:

var color_btns = document.querySelectorAll('#color > p');
color_btns.forEach(function(color) {
color.onclick = function () {
color_btns.forEach(function(element) {
if (element.classList.contains('selected')) {
element.classList.remove('selected');
}
});
color.classList.add('selected');
document.querySelector('#f_color').value = color.dataset.id;
};
});

我在IE&边缘:

对象不支持属性或方法"forEach">

在搜索了这个问题后,我了解到IE 9和更新版本应该支持这个功能。我试图自己定义函数,但没有成功。当我记录函数时,它被定义为一个函数(内部有"[native code]")。

我用for替换了每个.forEach,它运行得很好,

  • 但是我怎样才能让它工作呢
  • forEach()InternetExplorer&边缘

我以为是Array.prototype.forEach,而IE的最新版本(以及Edge的所有版本)都有。。。?

大多数DOM方法和集合属性实际上不是数组,它们是集合:

  • querySelectorAll返回一个静态NodeList(调用时匹配元素的快照)
  • ParentNode上的getElementsByTagNamegetElementsByTagNameNSgetElementsByClassNamechildren属性(元素是父节点)返回liveHTMLCollection实例(如果更改DOM,则该更改会实时反映在集合中)
  • getElementsByName返回实时NodeList(不是快照)

NodeList最近才得到forEach(以及keys和其他一些数组方法)。HTMLCollection没有也不会;事实证明,添加它们会破坏网络上太多的代码。

不过,NodeListHTMLCollection都是可迭代的,这意味着你可以用for-of循环它们,通过排列([...theCollection])将它们扩展到一个数组中,等等。但如果你在NodeList没有forEach的浏览器上运行,那么它可能太旧了,不可能有任何ES2015+功能,比如for-of或迭代。

由于NodeList被指定为具有forEach,因此您可以安全地对其进行polyfill,而且这真的很容易做到:

if (typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
NodeList.prototype.forEach = Array.prototype.forEach;
}

在这种情况下,直接赋值是可以的,因为enumerableconfigurablewritable都应该是true,并且它是一个值属性。(enumerable变成true让我很惊讶,但这就是它在Chrome/Chrmium/Edge/Etc、Firefox、旧的Legacy Edge和Safari上的原生定义)。

在您自己的代码中,如果您愿意,也可以使用HTMLCollection来实现这一点,只是要注意,如果您使用一些旧的DOM库,如MooTools或YUI或类似的库,如果您将forEach添加到HTMLCollection,它们可能会混淆。


正如我之前所说,NodeListHTMLCollection都被指定为可迭代的(因为这个Web IDL规则的缘故)。如果您运行的浏览器具有ES2015+功能,但由于某种原因不能使集合可迭代,那么您也可以polyfill:

if (typeof Symbol !== "undefined" && Symbol.iterator && typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype[Symbol.iterator]) {
Object.defineProperty(NodeList.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.iterator],
writable: true,
configurable: true
});
}

(HTMLCollection也是如此。)

下面是一个同时使用这两种功能的实际示例,请在IE11上尝试(例如)(尽管它只会演示forEach),而NodeList本身并不具备以下功能:

// Using only ES5 features so this runs on IE11
function log() {
if (typeof console !== "undefined" && console.log) {
console.log.apply(console, arguments);
}
}
if (typeof NodeList !== "undefined" && NodeList.prototype) {
// forEach
if (!NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
console.log("Added forEach");
NodeList.prototype.forEach = Array.prototype.forEach;
}
// Iterability - won't happen on IE11 because it doesn't have Symbol
if (typeof Symbol !== "undefined" && Symbol.iterator && !NodeList.prototype[Symbol.iterator]) {
console.log("Added Symbol.iterator");
Object.defineProperty(NodeList.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.iterator],
writable: true,
configurable: true
});
}
}
log("Testing forEach");
document.querySelectorAll(".container div").forEach(function(div) {
var html = div.innerHTML;
div.innerHTML = html[0].toUpperCase() + html.substring(1).toLowerCase();
});
// Iterable
if (typeof Symbol !== "undefined" && Symbol.iterator) {
// Using eval here to avoid causing syntax errors on IE11
log("Testing iterability");
eval(
'for (const div of document.querySelectorAll(".container div")) { ' +
'    div.style.color = "blue"; ' +
'}'
);
}
<div class="container">
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
</div>


这很令人困惑,因为HTMLCollection是可迭代的,但它没有用iterable声明标记,奇怪的是,在JavaScript DOM绑定中并不意味着某个东西可以迭代,它意味着它有forEachentrieskeysvalues它可以迭代。但是没有用iterable声明标记的HTMLCollection仍然是可迭代的。相反,由于前面提到的Web IDL规则,它是可迭代的。

好吧,让我们从这里开始,在JavaScript中,我们有一些情况,我们称之为类似的数组,这意味着即使它看起来像一个数组,它也不是真正的数组。。。

例如函数中的参数,或者在您的情况下的Nodelist。。。

即使所有的现代浏览器都知道你想把它改为Array,并且工作得很好,在IE和其他一些浏览器中,它不支持在Nodelist上使用数组函数。。。

因此,如果您支持广泛的浏览器,最好在对其进行任何活动之前将其转换为数组。。。

有几种方法可以将类似数组的值转换为真正的阵列。。。

ES5中广泛使用的一种结构是:

Array.prototype.stice.call(YourNodeList)

所以你可以做:

var allDivs = document.querySelectorAll("div");
var allRealDivsArray = Array.prototype.slice.call(allDivs);

但是,如果你使用ES6,还有更巧妙的方法可以做到这一点,只需确保你使用babel将它们转换为ES5,例如,因为那些不支持数组循环的旧浏览器肯定不会支持ES6。。。

两种非常常见的方法是:

1)使用阵列。

const allDivs = document.querySelectorAll("div");
const allRealDivsArray = Array.from(allDivs);

2)使用[…阵列]

const allDivs = document.querySelectorAll("div");
const allRealDivsArray = [...allDivs];

虽然它看起来像一个数组,但它实际上是一个NodeList,它与数组没有相同的功能。使用for循环代替

color_btns = document.querySelectorAll('#color > p'); 
for (var i = 0; i < color_btns.length; i++) {
color_btns[i].onclick = function () { 
for (var j = 0; j < color_btns.length; j++) {
if(color_btns[j].classList.contains('selected')) { 
color_btns[j].classList.remove('selected');
}
}
color_btns[i].classList.add('selected'); 
document.querySelector('#f_color').value = color_btns[i].dataset.id;
};
}

相关内容

最新更新