如何可靠地检查对象是 EcmaScript 6 Map/Set?

Posted

技术标签:

【中文标题】如何可靠地检查对象是 EcmaScript 6 Map/Set?【英文标题】:How to reliably check an object is an EcmaScript 6 Map/Set? 【发布时间】:2015-07-07 15:06:01 【问题描述】:

我只想检查一个对象是MapSet 而不是Array

要检查我正在使用 lodash 的 _.isArray 的数组。

function myFunc(arg) 
  if (_.isArray(arg)) 
    // doSomethingWithArray(arg)
  

  if (isMap(arg)) 
    // doSomethingWithMap(arg)
  

  if (isSet(arg)) 
    // doSomethingWithSet(arg)
  

如果我要实现isMap/isSet,它需要是什么样的?如果可能的话,我希望它能够捕获 Map/Set 的子类。

【问题讨论】:

【参考方案1】:

Vue源码中使用了以下方法。你可以借鉴这里的想法。

export const isMap = (val: unknown): val is Map<any, any> =>
  toTypeString(val) === '[object Map]'
export const isSet = (val: unknown): val is Set<any> =>
  toTypeString(val) === '[object Set]'


export const objectToString = Object.prototype.toString
export const toTypeString = (value: unknown): string =>
  objectToString.call(value)

这很容易理解。将对象转换为字符串后。然后你只需要检查字符串的值是 '[object Map]' 还是 '[object Set]'

我强烈建议前端阅读源代码中的实用功能。您总能找到最佳做法。

这里是 Vue 源的链接:https://github.com/vuejs/vue-next/blob/master/packages/shared/src/index.ts

你可以在这里找到我引用的代码。

【讨论】:

【参考方案2】:

你可以简单地使用:

export function isMap(item) 
  return !!item && Object.prototype.toString.call(item) === '[object Map]';


export function isSet(item) 
  return !!item && Object.prototype.toString.call(item) === '[object Set]';

除非这个方法的原型被覆盖

【讨论】:

是否严格需要布尔检查?似乎可以在没有虚假值的情况下使用。 是的,它是必需的,因为 object.toString 在 item 上被调用,如果它是 undefined 或 null 它会抛出错误 在 Chrome 和 Firefox 中,对于 Object.prototype.toString.call(null),我得到 "[object Null]" 和 "[objectUndefined]" 来调用 undefined【参考方案3】:

您可以使用instanceof 运算符:

function isSet(candidate) 
  return candidate instanceof Set;

如果候选对象的原型链中有Set.prototype,则instanceof 运算符返回true

edit — 虽然instanceof 的事情大部分会起作用,但在某些情况下它不会起作用,如 Bergi 的回答中所述。 p>

【讨论】:

即使该集合是Set 的子类,它也能工作吗?我应该指定,这是一个问题。 @nackjicholson 这取决于您如何创建子类。如果构造函数的.prototype 在对象的原型链中,则instanceof 运算符返回true @nackjicholson:只要你做class MySet extends Set,是的。 请注意,如果您正在为要重新分发的库编写代码,最好先检查是否定义了 window.Set(除非您的库包含用于旧浏览器的 shim)。 (或者让它在服务器上也能工作,你可以检查typeof Set != 'undefined')。 是的。实际上,在这种情况下情况更糟,因为与数组不同,Sets 仅在最新规范中。对旧浏览器使用 shim 是很常见的,我刚刚意识到 Object.prototype.toString 检查不适用于这些浏览器。我可能会在此处发布我最终要找到的答案。【参考方案4】:

这种情况类似于正确可靠地检测数组的 pre-ES5 方法。请参阅this great article,了解实现isArray 可能存在的缺陷。

我们可以使用

obj.constructor == Map/Set,但这不适用于子类实例(并且很容易被欺骗) obj instanceof Map/Set,但这仍然无法跨领域工作(并且可能被原型修改所欺骗) obj[Symbol.toStringTag] == "Map"/"Set",但那又容易被骗。

确实,我们需要测试一个对象是否具有[[MapData]]/[[SetData]] 内部插槽。这不是那么容易访问 - 它是内部的。不过,我们可以使用 hack:

function isMap(o) 
    try 
        Map.prototype.has.call(o); // throws if o is not an object or has no [[MapData]]
        return true;
     catch(e) 
        return false;
    

function isSet(o) 
    try 
        Set.prototype.has.call(o); // throws if o is not an object or has no [[SetData]]
        return true;
     catch(e) 
        return false;
    

对于一般用途,我推荐instanceof - 它简单、易懂、高效,适用于大多数合理的情况。或者你马上去打鸭子,只检查对象是否有has/get/set/delete/add/delete方法。

【讨论】:

为什么不使用Object.prototype.toString.call(o) == '[object Set]'?除了覆盖Object.prototype.toString的人之外,是否存在某些情况下会失败? (我认为被覆盖的 Object.prototype.toString 不值得担心 - 这是一个坏习惯,会破坏许多现有的库。) @MattBrowne Object.prototype.toString 等价于使用Symbol.toStringTag 方法——你不需要覆盖全局,它可以在每个对象的基础上被弄乱。

以上是关于如何可靠地检查对象是 EcmaScript 6 Map/Set?的主要内容,如果未能解决你的问题,请参考以下文章

掌握 4 大要点,玩转 ECMAScript 6 生成器!

ECMAScript 6.0

ECMAScript 6 Promise 对象

可靠地检查 NSDate 是不是在给定的小时、天或周内

ECMAScript 6 知识点梳理

如何检查Javascript Map是不是有对象键