【问题标题】:Checking whether something is iterable检查某事是否可迭代
【发布时间】:2013-09-23 22:07:58
【问题描述】:

在 MDN 文档中:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

for...of 构造被描述为能够迭代“可迭代”对象。但是有没有确定对象是否可迭代的好方法?

我试图找到数组、迭代器和生成器的通用属性,但一直没能做到。

除了在 try 块中执行 for ... of 并检查类型错误之外,是否有一种干净的方法来执行此操作?

【问题讨论】:

  • 当然,作为作者,你知道你的对象是否是可迭代的吗?
  • 对象作为参数传递,我不确定。
  • 为什么不测试参数的 typeof?
  • @andrew-buchan, James Bruckner:检查类型可能有效,但如果您阅读 MDN 文档,您会注意到它说“类似数组”。我不知道这究竟意味着什么,因此提出了这个问题。
  • wiki.ecmascript.org/doku.php?id=harmony:iterators 声明“如果对象具有iterator() 方法,则它是可迭代的。”。然而,由于这是一个草案,因此只有一个检查可能依赖于实施。你用什么环境?

标签: javascript


【解决方案1】:

如前所述,现在要测试 obj 是否可迭代

obj != null && typeof obj[Symbol.iterator] === 'function' 

历史答案(不再有效)

for..of 结构是 ECMASCript 第 6 版语言规范草案的一部分。所以它可能会在最终版本之前改变。

在此草案中,iterable 对象必须具有函数 iterator 作为属性。

您可以像这样检查对象是否可迭代:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}

【讨论】:

【解决方案2】:

检查可迭代性的正确方法如下:

function isIterable(obj) {
  // checks for null and undefined
  if (obj == null) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

为什么会这样(深度可迭代协议):https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

既然我们在谈论 for..of,我假设我们处于 ES6 思维模式。

此外,如果 obj 是字符串,则此函数返回 true 不要感到惊讶,因为字符串会遍历其字符。

【讨论】:

  • 或者,Symbol.iterator in Object(obj)
  • 使用“in”运算符有(至少)一个例外:字符串。字符串是可迭代的(就 for..of 而言),但您不能在其上使用 'in'。如果不是因为这个,我更喜欢使用 'in' 它看起来肯定更好。
  • 不应该是return typeof obj[Symbol.iterator] === 'function'吗? “为了可迭代,对象必须实现@@iterator 方法”——它指定方法
  • 对于 obj[Symbol.iterator] 来说,除了(未定义的或)函数之外,没有合适的语义。如果有人将例如 String 放在那里,这是一件坏事,如果代码尽快失败,IMO 很好。
  • typeof Object(obj)[Symbol.iterator] === 'function' 在所有情况下都有效吗?
【解决方案3】:

为什么这么冗长?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'

【讨论】:

  • Readability > Cleverness 总是返回 true
  • 哈哈,通常我是抱怨不可读代码的人,但实际上我认为这是相当可读的。就像一句英文句子:如果对象不为 null 并且符号迭代器属性是一个函数,那么它是可迭代的。如果这不是很简单,我不知道是什么......
  • imo,“可读性”的重点是在没有实际阅读的情况下了解正在发生的事情
  • @Alexander Mills 您的改进使代码变得更糟。 1. 就像@jfmercer 所说的Readability > Cleverness,所以将变量object 缩短为o 对任何人都没有帮助。 2.空字符串''是可迭代的,所以它必须返回true
  • 也许,您唯一需要阅读的就是名称:isIterable
【解决方案4】:

作为旁注,注意关于iterable的定义。如果您来自其他语言,您会期望可以迭代的东西,例如,for 循环是iterable。恐怕这里 iterable 意味着实现iteration protocol 的东西不是这种情况。

为了使事情更清楚,上面的所有示例都在此对象 {a: 1, b: 2} 上返回 false,因为该对象未实现迭代协议。因此,您将无法使用 for...of 对其进行迭代您仍然可以使用 for...in

因此,如果您想避免痛苦的错误,请通过如下所示重命名您的方法来使您的代码更加具体:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);

【讨论】:

  • 你没有任何意义。为什么你认为它会与undefined 决裂?
  • @adius 我想我错误地认为您在回答中使用了object !== null,但您使用的是object != null,因此在特定情况下它不会与undefined 发生冲突。我已经相应地更新了我的答案。
  • 好的,我明白了。顺便说一句:您的代码不正确。 hasIterationProtocol('') 必须返回true!你如何删除你的代码并离开可迭代的解释部分,这是唯一能在你的答案中增加真正价值/新东西的东西。
  • 它确实返回true,通过删除部分旧答案,我合并了两个函数并忘记了严格比较。现在我把原来的答案放回去了。
  • 我从没想过使用Object(...),很好。但在这种情况下,不需要空检查。
【解决方案5】:

最简单的解决方案其实是这样的:

function isIterable (value) {
  return Symbol.iterator in Object(value);
}

Object 将把任何不是对象的东西包装在一个中,即使原始值不是对象,in 运算符也能工作。 nullundefined 被转换为空对象,因此不需要边缘情况检测,字符串被包装成可迭代的 String 对象。

【讨论】:

  • Object创建一个新对象对于这种检查来说是浪费的。
  • 我不能说我确切地知道Object 函数是如何实现的,但是如果value 还不是一个对象,它只会创建一个新对象。我希望inObject(...) 的组合成为浏览器引擎可以轻松优化的东西,不像value !== undefined && value !== null && value[Symbol.iterator] && true。另外,它的可读性极强,我很在意。
  • 注意:JavaScript 有如此多的迭代/枚举/循环变体,以至于对名为isIterable 的函数的调用有点模棱两可。我可能会在需要时内联该代码,除非我真的需要将谓词传递给arr.filter(isIterable) 中的函数。或者你也可以按照 Casula 的方法调用函数hasIterationProtocol,罗嗦但明确
【解决方案6】:

对于 async 迭代器,您应该检查“Symbol.asyncIterator”而不是“Symbol.iterator”:

async function* doSomething(i) {
    yield 1;
    yield 2;
}

let obj = doSomething();

console.log(typeof obj[Symbol.iterator] === 'function');      // false
console.log(typeof obj[Symbol.asyncIterator] === 'function'); // true

【讨论】:

    【解决方案7】:

    如果您想检查变量是对象 ({key: value}) 还是数组 ([value, value]),您可以这样做:

    const isArray = function (a) {
        return Array.isArray(a);
    };
    
    const isObject = function (o) {
        return o === Object(o) && !isArray(o) && typeof o !== 'function';
    };
    
    function isIterable(variable) {
        return isArray(variable) || isObject(variable);
    }
    

    【讨论】:

      【解决方案8】:

      我正在寻找for ... in 的支票并决定如下。

      isIterable (value) {
        // add further checks here based on need.
        return Object.keys(Object(value)).length > 0
      }
      

      这将返回true 任何可迭代且至少有一个值的东西。 因此空字符串、空数组、空对象等将返回false。 但是{a: 'x', b:'y'} 会返回true

      【讨论】:

        【解决方案9】:

        如果对象具有属性Symbol.iterator,那么它是可迭代的。然后我们可以简单地检查obj是否像这样可迭代

        打字稿

        function isIterable(x: unknown): boolean {
          return !!x?.[Symbol.iterator];
        }
        

        或者作为箭头函数

        const isIterable = (x: unknown): boolean => !!x?.[Symbol.iterator];
        

        JavaScript

        const isIterable = x => !!x?.[Symbol.iterator];
        

        示例

        isIterable(["hello", "world"]); // true
        isIterable({}); // false
        isIterable(null); // false
        isIterable(undefined); // false
        isIterable(1); // false
        isIterable(true); // false
        isIterable(Symbol("foo")); // false
        isIterable(new Set()); // true
        isIterable(new Map()); // true
        

        【讨论】:

          【解决方案10】:

          2022 答案

          如果您问的是“foo 是否可迭代”,那么您可能来自一种语言(PHP、Python),其中该问题只有一个答案。 在现代 Javascript 中,存在不同类型的可迭代对象。因此,您必须根据要对变量执行的操作来检查迭代能力。

          TL;DR

          • 测试使用 forEach()!!foo.forEach 进行迭代的能力,在数组上返回 true。
          • 测试使用 for..of!!foo[Symbol.iterator] 进行迭代的能力,在数组或字符串上返回 true。
          • 测试使用 for..in!!Object.keys(Object(foo)).length 进行迭代的能力,在数组、字符串或对象上返回 true。

          长答案

          让我们定义一些变量:

          const someNumber = 42;
          42
          
          const someArray = [1,2,3];
          (3) [1, 2, 3]
          
          const someString = "Hello";
          "Hello, world!"
          
          const someObject = {a:"A", b:"B"};
          {a: "A", b: "B"}
          

          使用forEach() 测试可迭代性

          哪些类型可以用forEach()迭代,用!!foo.forEach测试:

          someNumber.forEach(x=>console.log(x));
          VM1526:1 Uncaught TypeError: someNumber.forEach is not a function at <anonymous>:1:12
          
          someArray.forEach(x=>console.log(x));
          VM916:1 1
          VM916:1 2
          VM916:1 3
          undefined
          
          someString.forEach(x=>console.log(x));
          VM957:1 Uncaught TypeError: someString.forEach is not a function at <anonymous>:1:12
          
          someObject.forEach(x=>console.log(x));
          VM994:1 Uncaught TypeError: someObject.forEach is not a function at <anonymous>:1:12
          

          似乎只有 Array 可以使用 forEach() 进行迭代。

          使用for..of 测试可迭代性

          哪些类型可以用for..of迭代,用!!foo[Symbol.iterator]测试:

          for (x of someNumber) { console.log(x); }
          VM21027:1 Uncaught TypeError: someNumber is not iterable at <anonymous>:1:11
              
          for (x of someArray) { console.log(x); }
          VM21047:1 1
          VM21047:1 2
          VM21047:1 3
          undefined
          
          for (x of someString) { console.log(x); }
          VM21065:1 H
          VM21065:1 e
          VM21065:1 l
          VM21065:1 l
          VM21065:1 o
          undefined
          
          for (x of someObject) { console.log(x); }
          VM21085:1 Uncaught TypeError: someObject is not iterable at <anonymous>:1:11
          

          Array 和 String 似乎可以用 for..of 进行迭代,但 Object 不是。并且 Number 和 Object 都抛出了错误。

          使用for..in 测试可迭代性

          哪些类型可以用for..in迭代,用!!Object.keys(Object(foo)).length测试:

          for (x in someNumber) { console.log(x); }
          undefined
          
          for (x in someArray) { console.log(x); }
          VM20918:1 0
          VM20918:1 1
          VM20918:1 2
          undefined
          
          for (x in someString) { console.log(x); }
          VM20945:1 0
          VM20945:1 1
          VM20945:1 2
          VM20945:1 3
          VM20945:1 4
          undefined
          
          for (x in someObject) { console.log(x); }
          VM20972:1 a
          VM20972:1 b
          undefined
          

          Array、String 和 Object 似乎都可以使用for..in 进行迭代。虽然它没有迭代,但 Number 没有抛出错误。

          在现代 ES6 Javascript 中,我看到 forEach 的使用频率远高于 for..infor..of。但是 Javascript 开发人员必须意识到这三种方法之间的差异,以及每种方法的不同行为。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-03-10
            • 2017-08-11
            • 1970-01-01
            • 2017-04-05
            • 2017-02-23
            • 2019-12-09
            • 2011-01-04
            • 2023-03-18
            相关资源
            最近更新 更多