如何在JavaScript中循环或枚举一个对象?

3570
我有一个类似以下的 JavaScript 对象:
var p = {
    "p1": "value1",
    "p2": "value2",
    "p3": "value3"
};
我如何循环遍历所有 p 元素 (p1p2p3...) 并获取它们的键和值?
48个回答

5152
你可以使用其他人展示的方式,使用 for-in 循环。然而,你还需要确保获得的键是对象实际的属性,而不是来自原型。

Here is the snippet:

var p = {
    "p1": "value1",
    "p2": "value2",
    "p3": "value3"
};

for (var key in p) {
    if (p.hasOwnProperty(key)) {
        console.log(key + " -> " + p[key]);
    }
}

使用 Object.keys() 代替 for-of:

var p = {
    0: "value1",
    "b": "value2",
    key: "value3"
};

for (var key of Object.keys(p)) {
    console.log(key + " -> " + p[key])
}

注意使用for-of来替代for-in,否则在命名属性上将返回undefined,Object.keys()可确保只使用对象自身的属性而非整个原型链的属性。

使用新的Object.entries()方法:

注意:该方法在Internet Explorer中不受本地支持,您可以考虑为旧版浏览器使用Polyfill。

const p = {
    "p1": "value1",
    "p2": "value2",
    "p3": "value3"
};

for (const [key, value] of Object.entries(p)) {
  console.log(`${key}: ${value}`);
}

378
在JavaScript中,每个对象都有一些内置的键-值对,其中包含元信息。当您循环遍历一个对象的所有键值对时,您也会循环遍历它们。hasOwnProperty()可以将这些过滤掉。 - danieltalsky
68
实际上,For...in并没有被弃用。For each...in 被弃用了。但我真的很喜欢术语“考古学家”……我以后得开始使用它。 - Ben Y
如何将循环中的值相加?谢谢。 - ERROR 401
(for..in) 用于对象,(for..of) 用于数组。 - mercury
2
对于任何关心性能的人来说,在正常使用中,for..in + hasOwnProperty 稍微比较快(https://jsben.ch/pCDAk),而在超过几千个属性时,`Object.keys` 明显更快(https://jsben.ch/ptzsL)。 - jason.
显示剩余2条评论

1368

在ECMAScript 5中,您可以结合使用Object.keys()Array.prototype.forEach()

var obj = { first: "John", last: "Doe" };

Object.keys(obj).forEach(function(key) {
    console.log(key, obj[key]);
});

ECMAScript 6 增加了 for...of

for (const key of Object.keys(obj)) {
    console.log(key, obj[key]);
}

ECMAScript 8 添加了 Object.entries(),避免了需要在原始对象中查找每个值的情况:

Object.entries(obj).forEach(
    ([key, value]) => console.log(key, value)
);

您可以结合使用for...of、解构和Object.entries

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

Object.keys()Object.entries() 的迭代顺序与 for...in 循环相同,但忽略原型链 。只迭代对象自身的可枚举属性。


29
标准为什么没有提供Object.forEach(obj, function (value, key) {...})?:( 当然,obj.forEach(function...)更短并且与Array.prototype.forEach相辅相成,但这可能会导致对象定义自己的forEach属性。我想Object.keys可以防止回调修改对象的键。 - David Harkness
3
Python非常容易,JavaScript我每次都要查基础知识。 - run_the_race

377
你必须使用for-in循环
但是,当使用这种循环时要非常小心,因为它会遍历原型链上的所有属性
因此,在使用for-in循环时,始终要使用hasOwnProperty方法来确定迭代中当前属性是否真正属于你正在检查的对象的属性:
for (var prop in p) {
    if (!p.hasOwnProperty(prop)) {
        //The current property is not a direct property of p
        continue;
    }
    //Do your logic with the property here
}

31
这个解决方案比Levik的更好,因为它只需要一个嵌套循环来执行主要逻辑,而不是两个,使得代码更易于阅读。虽然我会去掉continue周围的括号,它们是多余的。 - SystemicPlural
55
个人而言,我不会去掉 { },因为没有它们的 if 语句会让人难以理解哪些部分属于 if 语句的一部分,哪些不是。但我想这只是个人观点 :) - pimvdb
36
是的,我更喜欢保留 {},主要是为了避免混淆,如果以后需要向 if 作用域中添加内容,使用 {} 可以更加清晰明了。 - Andreas Grech
8
阅读我之前的评论,我意识到我没有使用正确的术语,因为我说“如果范围”;但请记住,JavaScript只有函数作用域。所以我实际上想表达的是“if块”。 - Andreas Grech

267

如果我们不提及循环对象的替代方法,这个问题就不完整了。

现在许多知名的JavaScript库都提供了自己的迭代集合方法,即针对数组对象类数组对象。这些方法方便易用,并且与任何浏览器完全兼容。

  1. 如果你使用 jQuery ,可以使用 jQuery.each()方法。它可无缝地遍历对象和数组:

$.each(obj, function(key, value) {
    console.log(key, value);
});
  • Underscore.js 中,您可以找到方法_.each(),它遍历元素列表,并将每个元素依次提供给提供的函数进行处理(注意在 iteratee 函数中参数的顺序!):

  • _.each(obj, function(value, key) {
        console.log(key, value);
    });
    
  • Lo-Dash提供了几种迭代对象属性的方法。基本的_.forEach()(或其别名_.each())可用于循环遍历对象和数组,但是(!)拥有length属性的对象被视为数组,并且为了避免这种行为,建议使用_.forIn()_.forOwn()方法(这些方法也具有将value参数放在第一位的特点):

  • _.forIn(obj, function(value, key) {
        console.log(key, value);
    });
    

    _.forIn()迭代对象的自有和继承可枚举属性,而_.forOwn()仅迭代对象的自有属性(基本上是针对hasOwnProperty函数进行检查)。对于简单对象和对象字面量,这些方法都可以很好地工作。

    通常,所有描述的方法在使用任何提供的对象时都具有相同的行为。除了使用原生的for..in循环通常比任何抽象,例如jQuery.each()更快,这些方法更易于使用,需要更少的编码并提供更好的错误处理。


    4
    为了获取值:$.each(obj, function (key, value) { console.log(value.title); }); (注:该代码片段使用jQuery库,目的是遍历一个对象并打印出每个属性的"title"值。) - Ravi Ram
    2
    有趣的是下划线和jQuery如何改变参数 :) - ppasler

    72

    前言:

    • 对象属性可以是自有的(属性存在于对象本身上)或继承的(不在对象本身上,而是在其原型之一上)。
    • 对象属性可以是可枚举的或不可枚举的。不可枚举属性被省略在很多属性枚举/数组中。
    • 属性名称可以是字符串或符号。名称为符号的属性被省略在很多属性枚举/数组中。

    在2018年,你遍历一个对象属性的选项如下(列表后面跟着一些例子):

    1. for-in [MDN, 规范] — 循环结构,循环遍历一个对象的可枚举属性(包括继承的属性),其名称为字符串。
    2. Object.keys [MDN, 规范] — 函数提供一个数组,其中包含一个对象的自身可枚举属性的名称,这些名称是字符串。
    3. Object.values [MDN, 规范] — 函数提供一个数组,其中包含一个对象的自身可枚举属性的
    4. Object.entries [MDN, 规范] — 函数提供一个数组,其中包含一个对象的自身可枚举属性的名称和值(数组中的每个条目都是一个[名称、值]数组)。
    5. Object.getOwnPropertyNames [MDN, 规范] — 函数提供一个数组,其中包含一个对象的自身属性的名称(即使是不可枚举的),这些名称是字符串。
    6. Object.getOwnPropertySymbols [MDN, 规范] — 函数提供一个数组,其中包含一个对象的自身属性的名称(即使是不可枚举的),这些名称是符号。
    7. Reflect.ownKeys [MDN, 规范] — 函数提供一个数组,其中包含一个对象的自身属性的名称(即使是不可枚举的),无论这些名称是字符串还是符号。
    8. 如果您想要包括非枚举继承属性的对象的所有属性,则需要在原型链上使用循环和Object.getPrototypeOf [MDN,
      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (const name in o) {
          const value = o[name];
          console.log(`${name} = ${value}`);
      }

      Object.keys (使用for-of循环,但您可以使用任何循环结构):

      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (const name of Object.keys(o)) {
          const value = o[name];
          console.log(`${name} = ${value}`);
      }

      Object.values:

      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (const value of Object.values(o)) {
          console.log(`${value}`);
      }

      Object.entries:

      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (const [name, value] of Object.entries(o)) {
          console.log(`${name} = ${value}`);
      }

      Object.getOwnPropertyNames:

      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (const name of Object.getOwnPropertyNames(o)) {
          const value = o[name];
          console.log(`${name} = ${value}`);
      }

      Object.getOwnPropertySymbols:

      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (const name of Object.getOwnPropertySymbols(o)) {
          const value = o[name];
          console.log(`${String(name)} = ${value}`);
      }

      Reflect.ownKeys:

      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (const name of Reflect.ownKeys(o)) {
          const value = o[name];
          console.log(`${String(name)} = ${value}`);
      }

      所有属性,包括继承的不可枚举属性:

      // A prototype object to inherit from, with a string-named property
      const p = {answer: 42};
      // The object we'll look at, which inherits from `p`
      const o = Object.create(p);
      // A string-named property
      o.question = "Life, the Universe, and Everything";
      // A symbol-named property
      o[Symbol("author")] = "Douglas Adams";
      for (let depth = 0, current = o; current; ++depth, current = Object.getPrototypeOf(current)) {
          for (const name of Reflect.ownKeys(current)) {
              const value = o[name];
              console.log(`[${depth}] ${String(name)} = ${String(value)}`);
          }
      }
      .as-console-wrapper {
        max-height: 100% !important;
      }


    1
    枚举/非枚举对象属性的不错添加。 - serraosays

    63

    你可以像这样遍历它:

    for (var key in p) {
      alert(p[key]);
    }
    

    请注意,key 不会取得属性的值,它仅仅是一个索引值。


    15
    这是重复的,而且并不完全正确。你需要检查 hasOwnProperty 才能使其正常工作。 - Vatsal
    4
    我最初根据上面的评论对这个答案进行了负评,直到我意识到这个答案是第一个发布的,因此不算是“重复”的。它可能不完整,但对许多情况来说已经足够好用了。 - But those new buttons though..

    58

    在ECMAScript 5中,您可以使用新的方法迭代字面量字段 - Object.keys

    更多信息,请参见MDN

    我的选择是以下内容,作为当前版本浏览器(Chrome30,IE10,FF25)的更快解决方案

    var keys = Object.keys(p),
        len = keys.length,
        i = 0,
        prop,
        value;
    while (i < len) {
        prop = keys[i];
        value = p[prop];
        i += 1;
    }
    

    您可以在jsperf.com上比较此方法与不同实现的性能:

    您可以在Kangax的兼容性表格中查看浏览器支持情况。

    对于旧版浏览器,您可以使用简易完整的polyfill。

    更新:

    此问题的所有最流行情况的性能比较可在perfjs.info上找到:对象字面量迭代


    48

    性能

    今天2020.03.06,我在MacOs High Sierra v10.13.6上测试了Chrome v80.0,Safari v13.0.5和Firefox 73.0.1上的选定解决方案。

    结论

    • 基于for-in(A,B)的解决方案对于所有浏览器的大型和小型对象都很快(或最快)
    • 令人惊讶的是,for-of(H)解决方案对于Chrome的小型和大型对象都很快
    • 基于显式索引i(J,K)的解决方案对于所有浏览器的小对象都很快(对于Firefox也适用于大对象,但在其他浏览器上中等速度)
    • 基于迭代器(D,E)的解决方案最慢,不建议使用
    • 解决方案C对于大对象很慢,对于小对象中等慢

    enter image description here

    细节

    执行性能测试的对象为:

    • 小对象-具有3个字段-您可以在此处在自己的机器上执行测试HERE
    • “大”对象-具有1000个字段-您可以在此处在自己的机器上执行测试HERE

    下面的片段介绍了使用的解决方案

    function A(obj,s='') {
     for (let key in obj) if (obj.hasOwnProperty(key)) s+=key+'->'+obj[key] + ' ';
      return s;
    }
    
    function B(obj,s='') {
     for (let key in obj) s+=key+'->'+obj[key] + ' ';
      return s;
    }
    
    function C(obj,s='') {
      const map = new Map(Object.entries(obj));
     for (let [key,value] of map) s+=key+'->'+value + ' ';
      return s;
    }
    
    function D(obj,s='') {
      let o = { 
        ...obj,
        *[Symbol.iterator]() {
          for (const i of Object.keys(this)) yield [i, this[i]];    
        }
      }
      for (let [key,value] of o) s+=key+'->'+value + ' ';
      return s;
    }
    
    function E(obj,s='') {
      let o = { 
        ...obj,
        *[Symbol.iterator]() {yield *Object.keys(this)}
      }
      for (let key of o) s+=key+'->'+o[key] + ' ';
      return s;
    }
    
    function F(obj,s='') {
     for (let key of Object.keys(obj)) s+=key+'->'+obj[key]+' ';
      return s;
    }
    
    function G(obj,s='') {
     for (let [key, value] of Object.entries(obj)) s+=key+'->'+value+' ';
      return s;
    }
    
    function H(obj,s='') {
     for (let key of Object.getOwnPropertyNames(obj)) s+=key+'->'+obj[key]+' ';
      return s;
    }
    
    function I(obj,s='') {
     for (const key of Reflect.ownKeys(obj)) s+=key+'->'+obj[key]+' ';
      return s;
    }
    
    function J(obj,s='') {
      let keys = Object.keys(obj);
     for(let i = 0; i < keys.length; i++){
        let key = keys[i];
        s+=key+'->'+obj[key]+' ';
      }
      return s;
    }
    
    function K(obj,s='') {
      var keys = Object.keys(obj), len = keys.length, i = 0;
      while (i < len) {
        let key = keys[i];
        s+=key+'->'+obj[key]+' ';
        i += 1;
      }
      return s;
    }
    
    function L(obj,s='') {
      Object.keys(obj).forEach(key=> s+=key+'->'+obj[key]+' ' );
      return s;
    }
    
    function M(obj,s='') {
      Object.entries(obj).forEach(([key, value]) => s+=key+'->'+value+' ');
      return s;
    }
    
    function N(obj,s='') {
      Object.getOwnPropertyNames(obj).forEach(key => s+=key+'->'+obj[key]+' ');
      return s;
    }
    
    function O(obj,s='') {
      Reflect.ownKeys(obj).forEach(key=> s+=key+'->'+obj[key]+' ' );
      return s;
    }
    
    
    
    // TEST
    
    var p = {
        "p1": "value1",
        "p2": "value2",
        "p3": "value3"
    };
    let log = (name,f) => console.log(`${name} ${f(p)}`)
    
    log('A',A);
    log('B',B);
    log('C',C);
    log('D',D);
    log('E',E);
    log('F',F);
    log('G',G);
    log('H',H);
    log('I',I);
    log('J',J);
    log('K',K);
    log('L',L);
    log('M',M);
    log('N',N);
    log('O',O);
    This snippet only presents choosen solutions

    以下是在Chrome浏览器中测试小对象的结果:

    在此输入图片描述


    38
    for(key in p) {
      alert( p[key] );
    }
    

    注意:您可以对数组执行此操作,但也会迭代 length 和其他属性。


    4
    使用类似这样的for循环时,"key"将只是一个索引值,它只会提示0、1、2等等...你需要访问p[key]。 - Bryan
    1
    这是 JavaScript 中最慢的数组迭代方法。您可以在计算机上检查此内容 - JavaScript 中遍历数组的最佳方法 - Pencroff
    7
    @Pencroff:问题在于这个问题并不是关于循环遍历数组的……;) - Sk8erPeter
    这是我在stackoverflow上不理解的事情。Richard给出了正确的答案,而且他是第一个给出这个答案的人,但他没有得到任何+1?@Bryan var p = {"p1":"q","p2":"w"}; for(key in p) { alert( key ); } 在警报中弹出“p1”和“p2”,那么有什么问题吗??? - Sebastian
    5
    我认为主要的区别在于质量:其他答案不仅告诉了如何做,还告诉了注意事项(例如原型)以及如何处理这些注意事项。在我看来,那些其他的答案比我的好 :). - Richard Levasseur

    37

    由于es2015越来越流行,我在这里发布了一个答案,其中包括使用生成器和迭代器平滑地迭代 [key,value] 对的用法。 正如其他语言(例如Ruby)所能实现的一样。

    好的,下面是代码:

    const MyObject = {
      'a': 'Hello',
      'b': 'it\'s',
      'c': 'me',
      'd': 'you',
      'e': 'looking',
      'f': 'for',
      [Symbol.iterator]: function*() {
        for (const i of Object.keys(this)) {
          yield [i, this[i]];
        }
      }
    };
    
    for (const [k, v] of MyObject) {
      console.log(`Here is key ${k} and here is value ${v}`);
    }

    您可以在 Mozilla 开发者页面上找到有关如何使用迭代器和生成器的所有信息。

    希望这对某些人有所帮助。

    编辑:

    ES2017 将包括 Object.entries,这将使得在对象中迭代 [key, value] 对变得更加容易。现在已知它将成为标准的一部分,根据 ts39 阶段信息。

    我认为是时候更新我的答案,让它比现在更加新鲜。

    const MyObject = {
      'a': 'Hello',
      'b': 'it\'s',
      'c': 'me',
      'd': 'you',
      'e': 'looking',
      'f': 'for',
    };
    
    for (const [k, v] of Object.entries(MyObject)) {
      console.log(`Here is key ${k} and here is value ${v}`);
    }

    您可以在MDN页面上了解更多有关用法的信息。

    这对我来说看起来完全是多余的。你会把它添加到系统中的每个对象吗?我认为提供迭代器的目的是让你可以执行 `for( const [k, v] of myObject )'。它只是额外提供了一些几乎没有价值的代码。 - Dean Radcliffe

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接