javascript、forEach 和 removeChild 不可预测的行为
javascript, forEach and removeChild unpredictable behavior
我想从列表中删除所有项目并替换为其他项目
var list = document.querySelector("ul");
[].forEach.call(list.childNodes, list.removeChild.bind(list));
上面的代码没有按预期工作,而是只删除了一半的项目(保留列表中的每隔一个项目)。
如果我将其更改为
var list = document.querySelector("ul");
[].slice.call(list.childNodes).forEach(list.removeChild.bind(list));
然后它按预期工作,
谁能解释一下?
在第一个中,你正在改变你正在迭代的数组。
第二个是复制然后遍历它。
以下是另一个不需要复制的选项:
for(; list.firstChild; list.removeChild(list.firstChild));
这会删除 firstChild
而不是 null
。
概念
为了解释第一种情况下的 "unpredictable" 行为,请考虑以下情况:
var array = [0, 1, 2, 3, 4, 5, 6, 7];
这使得行为更容易解释,而无需分散注意力的 .call()
和 .bind()
方法。
array.forEach(function(num, index) {
console.log(num, index);
array.splice(index, 1);
});
您可能想知道为什么输出是:
0 0
2 1
4 2
6 3
但其实很简单。 .forEach()
迭代索引直到 i < array.length
不再满足,而在每次迭代开始时,您的数组如下所示:
[0, 1, 2, 3, 4, 5, 6, 7];
^
0
[1, 2, 3, 4, 5, 6, 7];
^
1
[1, 3, 4, 5, 6, 7];
^
2
[1, 3, 5, 6, 7];
^
3
[1, 3, 5, 7];
^
(4 < array.length) !== true
这就是当您在调用 .forEach()
.
中操作正在迭代的数组时发生的情况
对于执行 [].slice.call(array)
的情况,您所做的只是对数组的所有索引进行浅表复制。这允许您迭代 copy 的 索引,同时从 original.
中删除节点
下面是一个综合示例,但请确保您的浏览器支持 ES6 模板字符串。
演示
var array = [0, 1, 2, 3, 4, 5, 6, 7];
document.write(`<p>original.forEach()</p>`);
array.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(index, 1);
});
document.write(`<pre>result: [${array}]</pre>`);
array = [0, 1, 2, 3, 4, 5, 6, 7];
var copy = array.slice();
document.write(`<p>copy.forEach()</p>`);
copy.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(array.indexOf(num), 1); // removing by reference, not by index
});
document.write(`<pre>result: [${array}]</pre>`);
body > * {
padding: 0;
margin: 0;
}
我想从列表中删除所有项目并替换为其他项目
var list = document.querySelector("ul");
[].forEach.call(list.childNodes, list.removeChild.bind(list));
上面的代码没有按预期工作,而是只删除了一半的项目(保留列表中的每隔一个项目)。 如果我将其更改为
var list = document.querySelector("ul");
[].slice.call(list.childNodes).forEach(list.removeChild.bind(list));
然后它按预期工作, 谁能解释一下?
在第一个中,你正在改变你正在迭代的数组。
第二个是复制然后遍历它。
以下是另一个不需要复制的选项:
for(; list.firstChild; list.removeChild(list.firstChild));
这会删除 firstChild
而不是 null
。
概念
为了解释第一种情况下的 "unpredictable" 行为,请考虑以下情况:
var array = [0, 1, 2, 3, 4, 5, 6, 7];
这使得行为更容易解释,而无需分散注意力的 .call()
和 .bind()
方法。
array.forEach(function(num, index) {
console.log(num, index);
array.splice(index, 1);
});
您可能想知道为什么输出是:
0 0
2 1
4 2
6 3
但其实很简单。 .forEach()
迭代索引直到 i < array.length
不再满足,而在每次迭代开始时,您的数组如下所示:
[0, 1, 2, 3, 4, 5, 6, 7];
^
0
[1, 2, 3, 4, 5, 6, 7];
^
1
[1, 3, 4, 5, 6, 7];
^
2
[1, 3, 5, 6, 7];
^
3
[1, 3, 5, 7];
^
(4 < array.length) !== true
这就是当您在调用 .forEach()
.
对于执行 [].slice.call(array)
的情况,您所做的只是对数组的所有索引进行浅表复制。这允许您迭代 copy 的 索引,同时从 original.
下面是一个综合示例,但请确保您的浏览器支持 ES6 模板字符串。
演示
var array = [0, 1, 2, 3, 4, 5, 6, 7];
document.write(`<p>original.forEach()</p>`);
array.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(index, 1);
});
document.write(`<pre>result: [${array}]</pre>`);
array = [0, 1, 2, 3, 4, 5, 6, 7];
var copy = array.slice();
document.write(`<p>copy.forEach()</p>`);
copy.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(array.indexOf(num), 1); // removing by reference, not by index
});
document.write(`<pre>result: [${array}]</pre>`);
body > * {
padding: 0;
margin: 0;
}