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