
每个出于各种目的使用出色的JavaScript的人都想知道:为什么typeof为null的 “对象” ? 函数的typeof返回“函数” ,但从数组 - “对象”返回 ? 在您自夸的课程中, getClass在哪里? 尽管在大多数情况下规范和历史事实可以轻松自然地回答,但我还是想为自己划清界限...更多。
读者,如果您的typeof和instanceof不足以完成您的任务,并且您需要一些细节而不是“ object” ,那么以后它会很有用。 哦,是的,关于鸭子:它们也一样,只是有点错误。
简要背景
在JavaScript中可靠地获取变量的类型一直是一项艰巨的任务,当然对于初学者而言并非如此。 在大多数情况下,当然不是必需的,只是:
if (typeof value === 'object' && value !== null) {
现在您不会捕获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” :。
对于内置对象(如Array或Date ),我们有一个秘密属性 [[Class]] ,可以通过toString将其与标准Object挂钩:
Object.prototype.toString.call(new Array); < "[object Array]"
现在我们有了类,并且自定义类型终于在该语言中得到了固定:这对您来说不是任何LiveScript; 我们大量编写支持的代码!
大约在同一时间, 出现了 Symbol.toStringTag和Function.name ,我们可以用它们以新的方式获取typeof 。
总的来说,在继续之前,我想指出的是,所考虑的问题随着语言的发展在StackOverflow上发展,并在编辑器之间不断发展: 9年前 , 7年前 , 不久前,或者是this and that 。
现状
之前,我们已经足够详细地研究了Symbol.toStringTag和Function.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。 如果该对象被重写为toStringTag ,则返回它;否则,返回它。
1.2。 如果该对象的构造函数是已知的,并且该函数具有name属性,则将其返回;否则,返回它。 - 否则,请使用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中声明函数的方法: FunctionDeclaration , FunctionExpression和最近的ArrowFunction 。 我们都知道何时使用什么:情况完全不同。 而且,如果我们从任何建议的选项所声明的函数中调用getTag ,我们将获得“ Function” 。
实际上,还有更多的方法来定义语言中的功能。 我们至少将考虑的ClassDeclaration添加到列表中,然后再添加异步函数和生成器等: AsyncFunctionDeclaration , AsyncFunctionExpression , AsyncArrowFunction , GeneratorDeclaration , GeneratorExpression , ClassExpression , MethodDefinition (列表不完整)。 他们似乎在说什么呢? 上面所有的行为都像一个函数-这意味着getTag也将返回“ Function” 。 但是有一个功能:所有选项当然都是功能,但不是全部都是直接功能-Function 。
功能的 内置子类型 :
Function构造函数被设计为可子类化的。
除了内置的GeneratorFunction和AsyncFunction子类之外,没有语法方法来创建Function子类的实例。
我们具有Function,并在GeneratorFunction和AsyncFunction内部及其“继承”的构造函数。 这强调了水槽和水槽有其独特的性质。 结果是:
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” 。
内联对象
我们有诸如Set , Map , Date或Error之类的东西 。 将getTag应用于它们将返回“ Function” ,因为它们是函数-可迭代集合,日期和错误的构造函数。 从实例中我们分别得到“ Set” , “ Map” , “ Date”和“ Error ”。
但是要当心! 仍然存在JSON或Math之类的对象。 如果您着急,您可以假设类似的情况。 但是不! 这是完全不同的-内置单个对象。 它们不是可实例化的( is not a constructor
)。 typeof调用将返回“ object” (怀疑)。 但是getTag将变成toStringTag并获得“ JSON”和“ Math” 。 这是我要分享的最后一个观察结果。
让我们远离狂热
最近,当我决定编写我的简单对象检查器以进行动态代码分析(玩转)时,我比平常更深入地询问了JavaScript中变量的类型。 由于您在生产中不需要它,因此该材料不仅在Abnormal编程中出版,而且还包含: typeof , instanceof , Array.isArray , isNaN以及执行必要的验证时需要记住的所有其他内容。 大多数项目具有TypeScript,Dart或Flow。 我就是爱JavaScript !