飞雪连天射白鹿,笑书神侠倚碧鸳

0%

通过数组边遍历边删除来回顾迭代器

遍历时删除会出现什么问题
迭代器是什么,for,forEach,for in,for of都是什么


遍历时删除会出现什么问题

先看这个例子,遍历数组时删除

1
2
3
4
5
6
7
8
9
10
11
12
var arr = [1,2,3]
for(var i = 0;i<arr.length;i++){
arr.splice(i,1)
console.log(i,arr)
}
// 或
arr.map((item,index)=>{
arr.splice(index,1)
console.log(arr)
})
// 0,[2,3]
// 1,[2]

发现结果和期望不一样,应该是个空数组
因为当修改原数组时,下一轮用的是这个新的数组,所以第二轮实际上是[2,3]的arr[1]

1
2
3
4
5
6
7
8
9
// 正序删除,需要更新下标
for(var i = 0;i<arr.length;i++){
arr.splice(i,1);
i--;
}
// 反序删除
for(var i = arr.length-1;i>=0;i--){
arr.splice(i,1);
}

另外,java中有iterator.remove()方法进行删除


for,forEach,for in,for of差异

通过MDN来简单回顾一下它们

for

常见的处理数组的循环嘛
for (let i = 0; i < 9; i++) {}

特点

  • 提前结束
    • break退出循环
    • continue提前结束本次循环,开始下一轮
  • 是所有循环语法糖的基础
  • 作用域
    • 使用 var 声明的变量与 for 循环处在同样的作用域中。用 let 声明的变量是语句的局部变量。

forEach

依旧是处理数组的循环,对每个元素依次执行一次指定函数

1
2
3
4
arr.forEach((item,index,_arr)=>{
// _arr是数组对象本身,不会改变原数组
console.log(item,index,_arr,this)
},this)

那些已删除或者未初始化的项将被跳过(例如稀疏数组[1,,3,,5])
(callback,thisArg) 还接收第二参数this,用法参照函数的this规则,默认指向window,见文末1

特点

  • 提前结束
    • throw抛出异常
  • 实现浅拷贝,和循环类似
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function copy(obj){
    const copy = Object.create(Object.getPrototypeOf(obj)) // 返回指定对象的原型
    const propNames = Object.getOwnPropertyNames(obj); // 返回属性的属性名
    propNames.forEach(function(name) {
    const desc = Object.getOwnPropertyDescriptor(obj, name); // 返回属性名对应的四大属性描述符configurable,enumerable,value,writable
    Object.defineProperty(copy, name, desc); // 更新对象的现有属性
    });
    return copy;
    }
  • 在遍历时删除会遇到同样的问题,因为每次都是以当前arr执行
  • 扁平化数组,建议使用flat()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function flatten(arr) {
    const result = [];
    arr.forEach((i) => {
    if (Array.isArray(i))
    result.push(...flatten(i));
    else
    result.push(i);
    })
    return result;
    }
    const problem = [1, 2, 3, [4, 5, [6, 7], 8, 9]];
    flatten(problem); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

map

和forEach一样都是遍历数组的每一项
map()返回新数组,forEach()返回undefined
map不会改变原数组,forEach会改变原数组

for in

任意顺序遍历一个对象的除Symbol以外的可枚举属性enumerable(包括它的原型链上的可枚举属性)

用于调试,可以更方便的去检查对象属性

1
2
3
for (var prop in obj) {
if (obj.hasOwnProperty(prop)){}
}

特点

  • 迭代过程中不要进行该对象的增删改
    • 不保证新属性会被访问到
  • 不建议用于数组
    • 因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素
  • 只迭代对象自身的属性
    • getOwnPropertyNames() 或 hasOwnProperty() 或 propertyIsEnumerable 确定某属性是否是对象本身的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 这个例子可以看到由于存在原型链继承规则
// 所以iterable要过滤,否则需要遍历很多不相关元素
Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
if (iterable.hasOwnProperty(i)) {
console.log(i); // logs 0, 1, 2, "foo"
}
}

for of

在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环

1
2
3
for (let item of arr) {
console.log(item);
}

特点

  • 弥补 forEach 和 for…in 的缺点
  • 提前结束
    • break, continue, throw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Array
let iterable = [10, 20, 30];
// 1 2 3

// String
let iterable = "boo";
// 'a' 'b' 'c'

// TypedArray
let iterable = new Uint8Array([0x00, 0xff]);
// 0 255

// Map
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);
// ["a", 1] ["b", 2] ["c", 3]

// Set
let iterable = new Set([1, 1, 2, 2, 3, 3]);
// 1 2 3

// arguments 对象
(function() {
for (let argument of arguments) {
console.log(argument);// 1 2 3
}
})(1, 2, 3);

// NodeList对象
let articleParagraphs = document.querySelectorAll("article > p");
// item.classList.add("read");

// 迭代生成器
function* fibonacci() {yield curr;}
for (let n of fibonacci()) {}

for in/of区别

迭代方式

for…in 语句以任意顺序迭代对象的可枚举属性
for…of 语句遍历可迭代对象定义要迭代的数据。

迭代器是什么

迭代器是包含next方法的对象

{done:是否结束,产生下一个值,value:返回值}
本质上,迭代器会不断调用其 next() 方法直到返回 done: true

Array 或 Map 等内置可迭代对象有默认的迭代行为
Object 则没有所以不能直接用for…of ,可以用Object.keys(obj),Object.entries(obj)

1
2
3
4
5
6
7
8
for..of
扩展运算符
yield*
解构赋值
内置API在接收数组参数时会调用数组的迭代行为
Array.from()
Map(),Set(),WeakMap(),WeakSet()
Promise.all()/Promise.race()



附录

这里来枚举一下forEach中的this情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var arr = [1,2,3]
var obj = {
arrFunc1:function(){
arr.forEach(function(item,index,_arr){
// 普通函数,普通回调,【检查】thisArg,this->arr
console.log(item,index,_arr,this)
},arr)
},
arrFunc2:function(){
arr.forEach((item,index,_arr)=>{
// 普通函数,箭头回调,【忽视】thisArg,this->obj
console.log(item,index,_arr,this)
},arr)
},
arrFunc3:()=>{
arr.forEach(function(item,index,_arr){
// 箭头函数,普通回调,【检查】thisArg,this->arr
console.log(item,index,_arr,this)
},arr)
},
arrFunc4:()=>{
arr.forEach((item,index,_arr)=>{
// 箭头函数,箭头回调,【忽视】thisArg,this->window
console.log(item,index,_arr,this)
},arr)
}
}
obj.arrFunc1()
听说,打赏我的人最后都找到了真爱
↘ 此处应有打赏 ↙
// 用户脚本