这篇文章是为了记录以下几个比较容易记忆混乱的方法

  • for…in (hasOwnProperty)
  • for…of
  • Object.keys()
  • Object.getOwnPropertyNames()

for…in

使用for..in循环时,返回的是所有能够通过对象访问的、可枚举的属性,既包括存在于实例中的属性,也包括存在于原型中的实例(实例+原型中的可枚举属性

遍历对象

for..in操作的主要目的就是遍历对象的属性,如果只需要获取对象自身的实例属性,可以使用hasOwnProperty()进行过滤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.getName = function() {
return this.name;
}

// 实例化
let cpp = new Person('cpp', 25);

for (let prop in cpp) {
console.log(prop); // name age getName
}

let hasOwn = Object.prototype.hasOwnProperty;
for (let prop2 in cpp) {
if (hasOwn.call(cpp, prop2)) {
console.log(prop2); // name age
}
}

遍历数组

for…in主要用用于遍历对象,但同样也可以遍历数组

1
2
3
4
let arr = [1,2,3,4,5]
for(let i in arr){
console.log(`索引${i}${arr[i]}`)
}

但是为什么大家都说不推荐使用for…in来遍历数组呢,总结了一下一共有三个理由

  1. 如果扩展了原生的Array,那么扩展的属性会被for..in输出

    for..in 会遍历原型中的属性,所以,无论是遍历数组还是对象,如果在原型中添加的属性的enumerable为true,都会被for..in 遍历出来

    可以使用Object.defineProperty添加,并设置 enumerable为false

  2. 对于不存在的数组项的处理

    对于数组来讲,我们知道如果将其length属性设置为大于数组项数的值,则新增的每一项都会取得undefined值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let colors = ['red','yellow','blue']
    colors.length = 5
    colors.push('orange')

    for(let i in colors){
    console.log(colors[i])
    }
    // 输出为 'red' 'yellow' 'blue' 'orange'

    for(let i = 0;i<colors.length;i++){
    console.log(colors[i])
    }
    // 输出为 'red' 'yellow' 'blue' undefined undefined 'orange'

    可见for..in 循环数组不会枚举不存在的项,但这些项在内存中是真实存在的,我们可以删除这些项,也可以给他们赋值。

  3. 遍历顺序

    数组索引只是具有整数名称的枚举属性,并且与通用对象属性相同。不能保证for … in将以任何特定的顺序返回索引。for … in循环语句将返回所有可枚举属性,包括非整数类型的名称和继承的那些。

    因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素

for…of 与 for…in 的区别

ES6新增for…of
for…of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句

无论是for…in还是for…of语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。

  • for…in 语句以原始插入顺序迭代对象的可枚举属性。

  • for…of 语句遍历可迭代对象定义要迭代的数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Object.prototype.objCustom = function() {}; 
    Array.prototype.arrCustom = function() {};

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

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

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

    for (let i of iterable) {
    console.log(i); // 3, 5, 7
    }

for…of 是根据 要迭代的数据的迭代器进行遍历的
迭代器可以自定义
需要定义要迭代数据的 Symbol.iterator

Object没有默认的迭代器,所以默认for…of不能用于Object

Object.keys()

Object.keys()用于获取对象自身所有的可枚举的,但不包括原型中的属性,然后返回一个由属性名组成的数组。
一个例子理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 遍历数组
let colors = ['red','yellow','blue']
colors.length = 5
colors.push('orange')
Array.prototype.demo = function () {};

Object.keys(colors); // ["0", "1", "2", "10"]

// 遍历对象
function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.demo = function() {}

var cpp = new Person('cpp', 25);

Object.keys(cpp); // ["name", "age"]

Object.getOwnPropertyNames()

Object.getOwnPropertyNames()方法返回对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组,但不会获取原型链上的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function A(a,aa) {
this.a = a;
this.getA = function() {
return this.a;
}
}
// 原型方法
A.prototype.aaa = function () {};

var B = new A('b', 'bb');
//自身方法
B.myMethodA = function() {};
// 不可枚举方法
Object.defineProperty(B, 'myMethodB', {
enumerable: false,
value: function() {}
});

Object.getOwnPropertyNames(B);
//结果 ["a", "getA", "myMethodA", "myMethodB"]

只获取自身不可枚举属性:使用 Object.getOwnPropertyNames() 获得所有自身属性,通过 Object.keys() 获得自身可枚举属性,通过filter函数筛选即可。

总结

名称 适用 描述
for in 数组 对象 实例+原型 的可枚举属性
for of 有迭代器的可迭代对象(Array,Map,Set等) 返回属性值
Object.keys() 数组 对象 获取对象自身所有的可枚举的属性
Object.getOwnPropertyNames() 数组 对象 获取对象的所有自身属性的属性名,包括不可枚举属性但不包括Symbol值作为名称的属性