for in 和for of 循环机制你理解透测了么
在面试题题中经常遇到面试官,问for..in 和for of 的区别,也许你只是只要它是两种遍历方式,for...in 可以遍历对象,for... of 不能遍历对象。除了这些的区别,就想不到什么东西了。今天就给大家盘点下,for...in. 和for... of 循环机制的优缺点,原理以及项目中的使用
for in 循环机制
1. for in 循环会优先迭代数字属性。
let obj ={
name:'wkm',
age:20,
[Symbol('AA')]:100,
0:10,
1:20
}
for (const key in obj) {
console.log("key",key);
}
/* 输出结果
key 0
key 1
key name
key age
*/
2. 无法迭代Symbol 类型的属性 (看上面循环没有输出Symbol 的属性)
3. 会遍历私有的和公有的可枚举属性
Object.prototype.AAA = 200;
Object.prototype[10] = 300
let obj ={
name:'wkm',
age:20,
[Symbol('AA')]:100,
0:10,
1:20
}
for (const key in obj) {
console.log("key",key);
}
/*
key 0
key 1
key name
key age
key 10
key AAA
*/
看上面结果,因为他遍历私有的和公有的可枚举属性,比如说项目中有个类,而且类的原型上有方法那么,用for in 去遍历它,原本不用它原型上的方法但是也会遍历到。浪费性能
基于上面的问题我们可以用obj.hasOwnProperty(key) 避免跌倒公有的属性
Object.prototype.AAA = 200;
Object.prototype[10] = 300
let obj ={
name:'wkm',
age:20,
[Symbol('AA')]:100,
0:10,
1:20
}
for (const key in obj) {
if(!obj.hasOwnProperty(key)) break // 避免迭代公有属性
console.log("key",key);
}
/*
key 0
key 1
key name
key age
*/
小结
从上面例子可以看出for in 循环的缺点 无法迭代Symbol 类型的属性, 会遍历私有的和公有的可枚举属性 尤其是会默认遍历公有的和私有的可枚举属性,对性能消耗会大。所以项目中尽量不用for in 循环,自己封装个遍历对象的方法。另外for in 除了可以遍历对象,还可遍历数组 和字符串,但是它不可以遍历 Set 和map 结构
封装循环对象的方法
- 会用到检测数据类型和检测是不是存对象的写法,下面先把工具函数封装一下
// 检测数据类型的方法封装
(function () {
var getProto = Object.getPrototypeOf; // 获取实列的原型对象。
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var fnToString = hasOwn.toString;
var ObjectFunctionString = fnToString.call(Object);
[
"Boolean",
"Number",
"String",
"Symbol",
"Function",
"Array",
"Date",
"RegExp",
"Object",
"Error"
].forEach(function (name) {
class2type["[object " + name + "]"] = name.toLowerCase();
});
function toType(obj) {
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[toString.call(obj)] || "object" :
typeof obj;
}
// 判断是不是存对象。
function isPlainObject(obj) {
var proto,
Ctor,
type = toType(obj);
if (!obj || type !== "object") { // 如果类型检测不是对象直接返回。-+
return false;
}
proto = getProto(obj); // 获取实列对象的原型对象。
if (!proto) {
return true;
}
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
}
window.toType = toType;
window.isPlainObject = isPlainObject;
})();
- 封装自己遍历对象的方法
const eachObject = function eachObject(obj,callback){
if(!isPlainObject(obj)) throw new TypeError('obj must be an plan object');
// 保证是个function
if(!(typeof callback =="function")) callback = Function.prototype;
// 获取遍历对象的键名 数组,有Symbol 属性加上去
let keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)),
i=0,
len = keys.length,
key,
value,
result;
// 为啥用 for 不用forEach 因为forEach 循环不能终止
for(;i<len;i++){
key= keys[i];
value = obj[key];
result = callback(key,value)
if(result === false) break;
}
return obj
}
// 测试
let obj={a:1,b:2,c:3}
eachObject(obj,(key,value)=>{
console.log(key,value)
})
for of 循环机制
1.for 0f 循环的原理
for of 循环是基于Iterator(遍历器的) 只要拥有Iterator 机制的数据结构都能用for of 循环
1.1 遍历器机制
遍历器(Iterator)是一种接口机制,为各种不同的数据结构提供了统一的访问机制,任何数据结构只要部署了Iterator接口,就可以用for of 进行循环
1.2 遍历器机制的特点
- 拥有next 方法用于依次遍历数据结构成员
- 每一次遍历都返回一个对象{done:false, value:xxxx}
- Done:记录遍历是否完成
- value: 当前遍历的结果
根据上面手写个遍历器机制
class Iterator {
constructor(assemble) {
this.assemble = assemble;
this.index = 0;
}
// 有个next 方法
next() {
let { index, assemble } = this;
if (index > assemble.length - 1) {
// 说明遍历完成
return {
done: true,
value: undefined
}
}
// 遍历未来完成
return {
done: false,
value: assemble[this.index++]
}
}
}
let itor = new Iterator([10, 20, 30]);
console.log(itor.next())
console.log(itor.next())
console.log(itor.next())
console.log(itor.next())
通过修改原来的遍历器机制实现数据隔一个循环一次
let arr = [10,20,30,40,50,60,70]
arr[Symbol.iterator] = function(){
let self = this;
index = -2;
return {
next(){
if(index>self.length-1){
return {
done:true,
value:undefined
}
}
return {
done:false,
value:self[index+=2]
}
}
}
}
for(let item of arr){
/*
循环过程:
1.首先获取[Symbol.iterator] 属性值函数 并将其执行拿到一个迭代器对象
2. 每一次循环都执行一次 iterator.next()-> {done,value}
3. 把value 的值给item 当done 为ture 时结束循环
*/
console.log(item)
}
2. 对象不具有遍历器机制,想让它用for of 循环,给它原型上加上遍历器
Object.prototype[Symbol.iterator] = function () {
let obj = this,
// 获取对象的键名,如果对象里有Symbol 属性拼接上,
keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
index = 0;
return {
next() {
if (index > keys.length - 1) {
return {
done: true,
value: undefined
}
}
return {
done: false,
value: obj[keys[index++]]
}
}
}
}
let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
console.log(value)
}
3 for of 小结
- 拥有Symbol.iterator属性的数据结构(都可以被遍历)
- for of 能遍历 数组,部分类数组, String Set Map
- 对象默认不具有遍历器机制,所以不能用for of 遍历 要想用for of 遍历,必须在对象原型上加遍历器
总结
for in 和for of 都是用来遍历的,for in 可以遍历 对象 字符串, 数组,但是由于for 会迭代原型上的可遍历属性,因此他的性能比较差,所以项目中迭代对象的方法做好要自己封装
for of 循环是只要数据结构的原型上Symbol.iterator 方法,都能迭代,数据结构原型上没有Symbol.iterator 方法的在原型上加上遍历器方法就可以了,迭代。
转载自:https://juejin.cn/post/7035854033424384007