一切的类型和鸭子的误解

图片


每个出于各种目的使用出色的JavaScript的人都想知道:为什么typeof为null的 “对象” ? 函数的typeof返回“函数” ,但从数组 - “对象”返回 ? 在您自夸的课程中, getClass在哪里? 尽管在大多数情况下规范历史事实可以轻松自然地回答,但我还是想为自己划清界限...更多。


读者,如果您的typeofinstanceof不足以完成您的任务,并且您需要一些细节而不是“ object” ,那么以后它会很有用。 哦,是的,关于鸭子:它们也一样,只是有点错误。


简要背景


在JavaScript中可靠地获取变量的类型一直是一项艰巨的任务,当然对于初学者而言并非如此。 在大多数情况下,当然不是必需的,只是:


if (typeof value === 'object' && value !== null) { // awesome code around the object } 

现在您不会捕获Cannot read property of null的本地类似物。 熟悉吗?


然后,我们开始越来越多地将函数用作构造函数,有时检查以这种方式创建的对象的类型有时很有用。 但是仅从实例中使用typeof是行不通的,因为我们正确地获得了“对象”


那时在JavaScript中使用原型OOP模型仍然很正常,还记得吗? 我们有一些对象,通过链接到其原型,我们可以找到构造函数属性,该属性指向创建对象的函数。 然后从函数和正则表达式中使用toString进行一些魔术处理,结果如下:


 f.toString().match(/function\s+(\w+)(?=\s*\()/m)[1] 

有时他们在面试中问这样的问题,但是为什么呢?


是的,我们可以将类型的字符串表示形式另存为特殊属性,并通过原型链从对象中获取它:


 function Type() {}; Type.prototype.typeName = 'Type'; var o = new Type; o.typeName; < "Type" 

您只需在函数声明和属性中编写两次“ Type” :。


对于内置对象(如ArrayDate ),我们有一个秘密属性 [[Class]] ,可以通过toString将其与标准Object挂钩:


 Object.prototype.toString.call(new Array); < "[object Array]" 

现在我们有了类,并且自定义类型终于在该语言中得到了固定:这对您来说不是任何LiveScript; 我们大量编写支持的代码!


大约在同一时间, 出现了 Symbol.toStringTagFunction.name ,我们可以用它们以新的方式获取typeof


总的来说,在继续之前,我想指出的是,所考虑的问题随着语言的发展在StackOverflow上发展,并在编辑器之间不断发展: 9年前7年前不久前,或者是this and that


现状


之前,我们已经足够详细地研究了Symbol.toStringTagFunction.name 。 简而言之,内部toStringTag字符是modern [[Class]] ,只有我们才能为我们的对象重新定义它。 几乎所有浏览器中的Function.name属性都被合法化为与示例相同的typeName :返回函数的名称。


您可以毫不犹豫地定义这样的功能:


 function getTag(any) { if (typeof any === 'object' && any !== null) { if (typeof any[Symbol.toStringTag] === 'string') { return any[Symbol.toStringTag]; } if (typeof any.constructor === 'function' && typeof any.constructor.name === 'string') { return any.constructor.name; } } return Object.prototype.toString.call(any).match(/\[object\s(\w+)]/)[1]; } 

  1. 如果变量是一个对象,则:
    1.1。 如果该对象被重写为toStringTag ,则返回它;否则,返回它。
    1.2。 如果该对象的构造函数是已知的,并且该函数具有name属性,则将其返回;否则,返回它。
  2. 否则,请使用Object对象toString方法,该方法将为我们完全完成所有其他变量的所有多态工作

具有toStringTag的对象:


 let kitten = { [Symbol.toStringTag]: 'Kitten' }; getTag(kitten); < "Kitten" 

具有toStringTag的类:


 class Cat { get [Symbol.toStringTag]() { return 'Kitten'; } } getTag(new Cat); < "Kitten" 

使用constructor.name


 class Dog {} getTag(new Dog); < "Dog" 

→在此存储库中可以找到更多示例


因此,现在很容易确定JavaScript中任何变量的类型。 该函数允许您统一检查变量的类型,并使用简单的开关表达式来代替多态函数中的鸭子检查 。 总的来说,我从不喜欢基于鸭子类型的方法,他们说如果某些东西具有splice属性,它是数组还是其他东西?


一些错误的鸭子


通过某些方法或属性的存在来了解变量的类型当然是每个人的工作,并且取决于情况。 但是我将使用此getTag并检查一些语言内容。


上课吗


我在JavaScript中最喜欢的“鸭子”是类。 那些开始使用ES-2015编写JavaScript的人有时并不怀疑这些类是什么。 事实是:


 class Person { constructor(name) { this.name = name; } hello() { return this.name; } } let user = new Person('John'); user.hello(); < "John" 

我们有一个class关键字,一个构造函数,一些方法,甚至extends 。 我们还通过new创建此类的实例。 在通常意义上看起来像一堂课-这意味着一堂课!


但是,当您开始实时向“类”中添加新方法时,同时新方法可立即用于已创建的实例时,某些方法会丢失:


 Person.prototype.hello = function() { return `Is not ${this.name}`; } user.hello(); < "Is not John" 

不要这样做!


并没有人可靠地知道这仅仅是原型模型的语法糖,因为从概念上讲,语言没有任何改变。 如果我们从Person调用getTag ,我们将得到“ Function” ,而不是虚构的 “ Class” ,这值得记住。


其他功能


有几种在JavaScript中声明函数的方法: FunctionDeclarationFunctionExpression和最近的ArrowFunction 。 我们都知道何时使用什么:情况完全不同。 而且,如果我们从任何建议的选项所声明的函数中调用getTag ,我们将获得“ Function”


实际上,还有更多的方法来定义语言中的功能。 我们至少将考虑的ClassDeclaration添加到列表中,然后再添加异步函数和生成器等: AsyncFunctionDeclarationAsyncFunctionExpressionAsyncArrowFunctionGeneratorDeclarationGeneratorExpressionClassExpressionMethodDefinition (列表不完整)。 他们似乎在说什么呢? 上面所有的行为都像一个函数-这意味着getTag也将返回“ Function” 。 但是有一个功能:所有选项当然都是功能,但不是全部都是直接功能-Function


功能的 内置子类型


Function构造函数被设计为可子类化的。
除了内置的GeneratorFunction和AsyncFunction子类之外,没有语法方法来创建Function子类的实例。

我们具有Function,并在GeneratorFunctionAsyncFunction内部及其“继承”的构造函数。 这强调了水槽和水槽有其独特的性质。 结果是:


 async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } getTag(sleep); < "AsyncFunction" 

同时,我们无法通过new运算符实例化此类函数,并且其调用将Promise返回我们:


 getTag(sleep(100)); < "Promise" 

具有生成器功能的示例:


 function* incg(i) { while(1) yield i += 1; } getTag(incg); < "GeneratorFunction" 

调用此函数将返回一个实例-Generator对象:


 let inc = incg(0); getTag(inc); < "Generator" 

对于接收器和生成器, toStringTag符号已被重新定义。 但是任何函数的typeof都将显示“ function”


内联对象


我们有诸如SetMapDateError之类的东西 。 将getTag应用于它们将返回“ Function” ,因为它们是函数-可迭代集合,日期和错误的构造函数。 从实例中我们分别得到“ Set”“ Map”“ Date”和“ Error ”。


但是要当心! 仍然存在JSONMath之类的对象。 如果您着急,您可以假设类似的情况。 但是不! 这是完全不同的-内置单个对象。 它们不是可实例化的( is not a constructor )。 typeof调用将返回“ object” (怀疑)。 但是getTag将变成toStringTag并获得“ JSON”“ Math” 。 这是我要分享的最后一个观察结果。


让我们远离狂热


最近,当我决定编写我的简单对象检查器以进行动态代码分析(玩转)时,我比平常更深入地询问了JavaScript中变量的类型。 由于您在生产中不需要它,因此该材料不仅在Abnormal编程中出版,而且还包含: typeofinstanceofArray.isArrayisNaN以及执行必要的验证时需要记住的所有其他内容。 大多数项目具有TypeScript,Dart或Flow。 我就是爱JavaScript

Source: https://habr.com/ru/post/zh-CN427253/


All Articles