我正在制作一个关于产品(颜色等)的选择脚本,它适用于除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
上的getElementsByTagName
、getElementsByTagNameNS
、getElementsByClassName
和children
属性(元素是父节点)返回liveHTMLCollection
实例(如果更改DOM,则该更改会实时反映在集合中)getElementsByName
返回实时NodeList
(不是快照)
NodeList
最近才得到forEach
(以及keys
和其他一些数组方法)。HTMLCollection
没有也不会;事实证明,添加它们会破坏网络上太多的代码。
不过,NodeList
和HTMLCollection
都是可迭代的,这意味着你可以用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;
}
在这种情况下,直接赋值是可以的,因为enumerable
、configurable
和writable
都应该是true
,并且它是一个值属性。(enumerable
变成true
让我很惊讶,但这就是它在Chrome/Chrmium/Edge/Etc、Firefox、旧的Legacy Edge和Safari上的原生定义)。
在您自己的代码中,如果您愿意,也可以使用HTMLCollection
来实现这一点,只是要注意,如果您使用一些旧的DOM库,如MooTools或YUI或类似的库,如果您将forEach
添加到HTMLCollection
,它们可能会混淆。
正如我之前所说,NodeList
和HTMLCollection
都被指定为可迭代的(因为这个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绑定中并不意味着某个东西可以迭代,它意味着它有forEach
、entries
、keys
、values
、和它可以迭代。但是没有用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;
};
}