侧边栏壁纸
博主头像
woku博主等级

学可以无术,但不能不博。 术可以无量,但不能不专。

  • 累计撰写 4 篇文章
  • 累计创建 11 个标签
  • 累计收到 2 条评论

循环,迭代,遍历,枚举

woku
2022-11-19 / 0 评论 / 0 点赞 / 56 阅读 / 2,722 字

概览

循环(loop):计算机底层提供的多次运行同一程序的机能。循环是这一机能的接口。比如for关键字。
迭代:建立在遍历循环的基础上。在遍历的过程中,每一个部分就叫做一次迭代。
从1.0到2.0到3.0这个是遍历的过程
从1.0升级到2.0这一个部分是一次迭代。从2.0升级到3.0这一个部分又是一次迭代
image
遍历:针对一组数据进行按顺序抽取的行为。

var arr = [1, 2, 3, 4, 5]
// 通过一个程序,先输出1,再输出2,再输出3,再输出4,再输出5.这样的一个过程就是遍历。
// 输出1,一次迭代
// 输出2,一次迭代
// 输出3,一次迭代
// ...

枚举:在一个集合中,无序的抽取其中成员的过程叫做枚举
比如在一个班级中,随机的进行点名,点名组成的集合是无序性,那么这样的一个过程就是枚举。

遍历,迭代必须是有顺序的
枚举是针对无序的

循环和遍历对比
循环:底层的一种机制,是语言提供的
遍历:针对数据的

var arr = [1, 2, 3];

for (var i = 0; i < arr.length; i++) {
  console.log(i);
}
// 循环并不是遍历数据,而是重复的执行console.log(i)
// 取i的值的过程并不是循环给你做的,是数组提供的一种能力
// 你可以通过break, continue, retrun来控制循环



arr.forEach(item => {
  console.log(item)
})
// forEach是数组提供的遍历方法,是针对于数据的
// 遍历的过程必须完整

数据的有序和无序性

有序

看下面声明的数组,他们的数据意义是否一样。

var arr = [1, 2, 3];
var arr2 = [3, 2, 1];

数据意义不一样
arr 0 -> 1 1 -> 2 2 -> 3
arr2 0 -> 3 1 -> 2 2 -> 1

数组是有顺序的,是有序列表,可迭代
其他的有序列表比如Set Map String

var str = '1234'
var str2 = '4321'
// 顺序不一样,数据意义也不一样,string也是有序列表
// 字符串可以调用数组上的一些方法,因为他们是具有共通性的

无序

看下面声明的对象,他们的数据意义是否一样。

var obj = {
  a: 1,
  b: 2,
};

var obj2 = {
  b: 2,
  a: 1,
};

数据意义一样
obj a=> 1 b=>2
obj2 b=>2 a=>1
不管他们在对象里的顺序如何,a的值就是1,b的值就是2

对象是无序的,不可迭代

类数组(Array-Like)

var obj = {
  0: 1,
  1: 2,
  2: 3,
  3: 4,
  length: 4,
};
// 模拟数组的有序性
// 类数组的数据意义 === 数组
var obj = {
  0: 1,
  1: 2,
  2: 3,
  3: 4,
};
// 这也是类数组

添加push方法,给类数组添加一项。

var obj = {
  0: 1,
  1: 2,
  2: 3,
  3: 4,
  length: 4,
  push: Array.prototype.push,
};
obj.push(5);
console.log(obj); // {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, length: 5}
var obj = {
  0: 1,
  1: 2,
  2: 3,
  3: 4,
  push: Array.prototype.push,
};
obj.push(5);
console.log(obj);
// 没有写length属性,那么默认为0,push就根据length这个值,在对应的后面增加
// 打印的就是 {0: 5, 1: 2, 2: 3, 3: 4, 4: 5, length: 1}
var obj = {
  0: 1,
  1: 2,
  2: 3,
  3: 4,
  length: 3,
  push: Array.prototype.push,
};
obj.push(5);
console.log(obj);
// length为3,push的时候在序号为4的位置,设置为5
// 打印的就是 {0: 1, 1: 2, 2: 3, 3: 5, length: 5}

模拟的有序,是在开发层面上表示为有序
在底层,这个本质上还是个对象,是无序的(不可迭代),只不过自己写的属性为0,1, 2,,3

类数组存在的意义

在DOM中,获取HTMLCollection是一个有序列表,在表示有序列表的同时,我们还可以放入一些自己的属性或者方法。

var arrLike = {
  0: li HTMLElement,
  1: li HTMLElement,
  2: li HTMLElement,
  a: 'xxx',
  b: 'xxx',
  test: function(){}
}

类数组可以将有序的集合和无序的组合到了一起
数组是有序的集合,数组添加方法或属性只能通过 [].test = function(){}这种方式

循环和循环枚举

循环

for (let i = 0; i < 2; i++) {
  console.log(i);
}

相当于下面写法

let i = 0;
for (; i < 2; ) {
  console.log(i);
  i++;
}

  • 声明i = 0
  • 0 < 2 yes
  • console.log(i) 输出0
  • i++ i => 1
  • 1 < 2 yes
  • console.log(i) 输出1
  • i++ i => 2
  • 2 < 2 no
  • break

循环枚举

使用for枚举对象

var obj = {
  a: 1,
  b: 2
}

使用循环方式,枚举出对象的键和值

var obj = {
  a: 1,
  b: 2,
};

const keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
  console.log(i, keys[i], obj[keys[i]]);
}

注意:Object.keys只能获取到可枚举的属性名

const obj = {};
Object.defineProperties(obj, {
  a: {
    value: 1,
    enumerable: true,
  },
  b: {
    value: 2,
    enumerable: true,
  },
  length: {
    value: 2,
    // 不设置enumerable默认为false,不可枚举
  },
});

const keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
  console.log(i, keys[i], obj[keys[i]]);
}

通过Object.defineProperties这种方式来定义的属性,设置不可枚举,还能不能把属性给拿出来呢?

getOwnPropertyNames是更底层抛出的api

const obj = {};
Object.defineProperties(obj, {
  a: {
    value: 1,
    enumerable: true,
  },
  b: {
    value: 2,
    enumerable: true,
  },
  length: {
    value: 2,
  },
});

// const keys = Object.keys(obj);
const keys = Object.getOwnPropertyNames(obj);
for (var i = 0; i < keys.length; i++) {
  console.log(i, keys[i], obj[keys[i]]);
}
// 0 'a' 1
// 1 'b' 2
// 2 'length' 2

使用for-in枚举对象

var obj = {
  a: 1,
  b: 2,
};
for (var key in obj) {
  console.log(key, obj[key]);
}
// for-in会把自己定义在原型上的属性也遍历出来
var obj2 = {
  a: 1,
  b: 2,
};

obj2.__proto__.c = 3;
for (let k in obj2) {
  console.log(k); // a, b, c
}

自己实现的forIn

var obj = {
  a: 1,
  b: 2,
};

function forIn(obj, callback) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    typeof callback === "function" && callback(keys[i], obj);
  }
}

forIn(obj, function (key, obj) {
  console.log(key, obj[key]);
});

for-in封装

var obj = {
  a: 1,
  b: 2,
};

obj.__proto__.c = 3;
Object.prototype.forInObj = function (callback) {
  var obj = Object(this);
  var res = [];
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      if ({}.toString.call(obj[key]) === "[object Object") {
        res = [key, obj[key].value];
      } else {
        res = [key, obj[key]];
      }
      typeof callback === "function" && callback(...res);
    }
  }
};

obj.forInObj(function (key, value) {
  console.log(key, value);
});

注意: Object.keys 不会包含原型上自定义的属性
for-in 会包含自身的属性和原型上自定义的属性

var obj = {
  a: 1,
  b: 2,
};

obj.__proto__.c = 3;
Object.prototype.forInObj = function (callback) {};

console.log(Object.keys(obj));  // [a, b]

for (var key in obj) {
  console.log(key);
  // a
  // b
  // c
  // forInObj
}

循环迭代

对于可迭代对象,我们使用for-of进行循环迭代

可迭代对象有: Array、Map、Set、String、arguments(类数组,底层进行了包装,是一个可迭代对象)

var arr = [1, 2, 3];
for (var v of arr) {
  console.log(v);
}

function test(a, b, c) {
  for (var v of arguments) {
    console.log(v);
  }
}
test(1, 2, 3);

var liItems = document.getElementsByClassName("item");
var liItems2 = document.querySelectorAll(".item");
console.log(liItems);  // HTMLCollection
console.log(liItems2); // NodeList
for (var v of liItems) {
  console.log(v);
}

for (var v of liItems2) {
  console.log(v);
}

  • 能使用for of进行循序迭代的,都具有一个特点,就是在其原型上部署了一个Symbol(Symbol.iterator)的接口.
  • for of是通过iterator迭代器实现出来的。这个数据必须要继承这个方法,for of才能调用这个方法内部的next进行一次次的迭代。

image-1668868744970

  • 对于对象来说,是不能使用for of
var obj = {
  a: 1,
  b: 2,
};
for (var v of obj) {
  console.log(v);
}

直接报错: Uncaught TypeError: obj is not iterable => obj是不可迭代的
打印obj,上面没有部署Symbol(Symbol.iterator)接口
image-1668868767348

  • for of循环迭代流程
// Array.prototype[Symbol.iterator] = function () {
//    实现了什么
// };

function* generation(iterableObject) {
  // 生成器 -> 生成迭代器 迭代器.next()表示迭代一次
  // for of每迭代一次,会执行iterator.next(), 返回{value: ???, done: true/false }, for of拿到这个value值
  // [1, 2, 3] for of  -> iterator.next() -> {value: 1, done: false}  1
  //           继续迭代 -> iterator.next() -> {value: 2, done: false}  2
  //           继续迭代 -> iterator.next() -> {value: 3, done: false}  3
  //           继续迭代 -> iterator.next() -> {value: undefined, done: true}  迭代结束
  for (var i = 0; i < iterableObject.length; i++) {
    yield iterableObject[i];
  }
  /**
   * yield 产出值 {value: ???, done: true/false } 停下来。
   */
}

var iterator = generation([1, 2, 3]);
console.log(iterator.next()); // 迭代接口
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

// next是提供给for of的一个接口

for of就是一次又一次的调用next的过程

  • 迭代接口实现
function generation(iterableObject) {
  let nextIndex = 0;
  function next() {
    return nextIndex < iterableObject.length
      ? { value: iterableObject[nextIndex++], done: false }
      : { value: undefined, done: true };
  }
  return {
    next
  };
}
var iterator = generation([1, 2, 3]);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

  • 对于对象,如果需要使用for of,那么可以手动的部署一个Symbol(Symbol.iterator)接口
var obj = {
  a: 1,
  b: 2,
  c: 3,
};
Object.prototype[Symbol.iterator] = function values() {
  var obj = Object(this);
  var nextIndex = 0;
  function next() {
    var keys = Object.keys(obj);
    return nextIndex < keys.length
      ? { value: [keys[nextIndex], obj[keys[nextIndex++]]], done: false }
      : { value: undefined, done: true };
  }
  return {
    next,
  };
};

for (var [key, value] of obj) {
  console.log(key, value);
}
console.log(obj);

遍历与数组扩展方法

forEach
map
reduce
reduceRight
filter
some
every
以上都是针对数组数据的

  • forEach

一般的遍历方法,要取出某项的值进行操作可以使用此方法

var arr = [1, 2, 3];
arr.forEach(function (item, index, arr) {
  arr[index] += 1;
});
console.log(arr);

forEach的第二个参数是更改第一个参数函数中的this指向

var arr = [1, 2, 3];
arr.forEach(
  function (item, index, arr) {
    arr[index] += this.a;
  },
  { a: 1 }
);
console.log(arr);
  • map (映射)

对数组进行相应的加工,返回一个新数组

var arr = [1, 2, 3];
var arr2 = arr.map(item => (item += 1));
console.log(arr2); // [2, 3, 4]
console.log(arr); // [1, 2, 3]
  • filter (过滤)

对数组进行过滤,返回一个新数组
一般用于数组的删除,得到一个新数组,尽量不影响原来的数组

var arr = [1, 2, 3];
var arr1 = arr.filter(function (item) {
  if (item > 1) {
    return item;
  }
});
console.log(arr1);

  • reduce (归纳,收集)
var arr = [1, 2, 3];
var arr2 = ["a", "b", "c"];


const obj = arr.reduce(function (pre, next, index) {
  pre[arr2[index]] = next;
  return pre;
}, {});
console.log(obj);
// reduce的第二个参数表示初始值,初始值是什么,reduce就返回什么
var arr = [1, 2, 3];
var arr2 = ["a", "b", "c"];


const obj = arr.reduce(function (pre, next, index) {
  pre[index] = arr2[index] + "-" + next;
  return pre;
}, []);
console.log(obj);
  • some

some只要找到一个满足条件,就停止了。

  • every

every只要找到一个不满足的,就停止了

some和every会停止,不是遍历方法,只是用了循环迭代。迭代的过程中,找到了符合条件的就停止。

0

评论区