先做一个测试题:
const arr = [1, 2, 3]
arr.name = 'name'
// 不要这么做,只是为了测试
Array.prototype.test = function () {}
Object.defineProperty(arr, 'newPro1', { value: '1' })
Object.defineProperty(arr, 'newPro2', { value: '1', enumerable: true })
for (const i in arr) {
console.log(typeof i, i)
// 依次打印出 ...
}
for (const v of arr) {
console.log(v) // 依次打印出 ...
}
arr.forEach((v, i, array) => {
console.log(typeof i, i, v)
// 依次打印出 ...
})
console.log(Object.keys(arr)) // 结果
console.log(Object.getOwnPropertyNames(arr))// 结果
使用不同的方法打印的结果是什么
答案:
for (const i in arr) {
console.log(typeof i, i)
// 依次打印出 string '0'、string '1'、string '2'、string 'name'、string 'newPro2'、string 'test'
}
for (const v of arr) {
console.log(v) // 1、2、3
}
arr.forEach((v, i, array) => {
console.log(typeof i, i, v)
// 依次打印出number 0 1 、number 1 2、number 2 3
})
console.log(Object.keys(arr)) // ['0', '1', '2', 'name', 'newPro2']
console.log(Object.getOwnPropertyNames(arr)) // ['0', '1', '2', 'length', 'name', 'newPro1', 'newPro2']
由此可以得到for in 、for of 、forEach的区别
for in
循环遍历对象(数组也属于对象)的除Symbol外的可枚举属性,包括从其父类原型上的继承到的可枚举的属性,(js中基本包装类型的原型属性是不可枚举的),也可以遍历字符串,返回每个字符对应的索引
(补充)可枚举属性:
对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。
在使用Object.defineProperty()为对象添加属性是会设置enumerable值
for in 特点:
- for in 在遍历数组arr [1,2,3]时会把其索引作为属性,值得注意的是for in会把遍历出来的key值都转换为string形式,所以即使是索引也不是number形式
- 直接在arr上添加赋值的属性为可枚举属性
- for in中可以使用break、continue、return 退出循环(return语句只能在function中,如果要使用return for in 必选得在一个function中)
- 通过Object.defineProperty添加的newPro1,newPro2只有newPro2被枚举到,是因为定义newPro1时未设置enumerable: true ,其默认值为false表示不可枚举
- 基本包装类型的原型属性是指js对象原本的原型属性如:Object.toString、Object.hasOwnProperty、Array.isArray 这些属性是不可以枚举的
- 通过Array.prototype.test = function () {}为Array添加的原型属性不属于3中提到的基本包装类型的原型属性,arr 的父类为Array,Array上新增的原型属性被arr继承,for in 可以枚举到。由于Array比较特殊,下面这个列子可能会更明白
function Person (name) {
this.name = name
}
Person.prototype.pro1 = 'pro1'
const p = new Person('test')
for (const i in p) {
console.log(i) // 依次输出"name" "pro1"
}
缺点:
- 遍历出来的key值都将被转换为字符串形式,遍历数组时存在转换成本,某些场景下可能会遇到问题,不建议与数组一起使用
- 会枚举出继承的prototype属性。(除js中基本包装类型的原型属性)
for of
for of用于循环遍历具有iterable接口的数据的值,不能用于普通对象
原生具备Iterator接口的数据结构有:
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
forEach
forEach只能用于Array、Set、Map的遍历
arr.forEach((value,index,arr)=>{})
forEach和for of功能相似:
区别:for of可以使用continue、break、return退出循环,forEach不可以
Object.keys(obj)
返回对象自身的除Symbol外的可枚举属性,和for in不同的是,Object.keys不会遍历继承的属性
Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回自身的除Symbol外的所有属性名称的组成的数组,其和Object.keys不同的是,Object.keys只能遍历可枚举的属性,getOwnPropertyNames可遍历所有的不管是否可枚举的属性
const obj = { pro1: 'pro1' }
Object.defineProperty(obj, 'newPro1', { value: 'newPro1', enumerable: true })
Object.defineProperty(obj, 'newPro2', { value: 'newPro2', enumerable: false })
console.log(Object.keys(obj))
console.log(Object.getOwnPropertyNames(obj))
总结(区别)
for in和for of:
for in可以用于对象、数组、字符串的遍历不能用于Map、Set。
for of可以用于具备Iterator接口的数据结构的遍历,不能用于原生对象。
for of 和forEach:
for of中可以使用break、return退出循环,也可以使用continue跳过本次循环后续操作
forEach中只能通过try catch等方式退出循环,通过return跳过本次循环后续操作
for in 和Object.keys()、Object.getOwnPropertyNames
- for in 会返回继承到的原型属性,Object.keys和Object.getOwnPropertyNames只返回自身的属性
- for in 和Object.keys只能遍历到可枚举的属性,getOwnPropertyNames可以同时返回可枚举和不可枚举的属性