typeof Everythingとカモの誤解

画像


誰もが疑問に思ったどんな目的でも素晴らしいJavaScriptを使用しています 。なぜtypeof nullは "オブジェクト"なのですか? 関数のtypeof"function"を返しますが、 Array- "object"からは ? 自慢のクラスでgetClassどこにありますか? そして、大部分の仕様歴史的事実は簡単かつ自然に答えますが、私はもう少し線を引きたいです...私自身のために。


読者、あなたのtypeofinstanceofがあなたのタスクで十分ではなく、 「オブジェクト」ではなくある種の特異性が必要な場合、それは後で役立つでしょう。 そうそう、アヒルについて:彼らも、少し間違っているでしょう。


簡単な背景


JavaScriptで変数の型を確実に取得することは、初心者にとってはもちろん、決して簡単な作業ではありません。 ほとんどの場合、もちろん必須ではありません。


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

NPEのローカルアナログで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」を記述する必要があるのは2回だけです。


組み込みオブジェクト( ArrayDateなど )には、 秘密のプロパティ [[Class]]がありました。これは、標準のObjectからtoStringを介してフックできます。


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

これでクラスが作成され、カスタムタイプが最終的に言語で修正されました。これはLiveScriptではありません。 サポートされているコードを大量に記述します!


同じ頃、 Symbol.toStringTagFunction.nameが登場しました。これらを使用して、 typeofを新しい方法で取得できます。


一般に、先に進む前に、検討中の問題は言語とともにStackOverflowで進化し、編集委員会から編集委員会に上昇することに注意してください。 9年前7年前それほど はありません


現状


前に、 Symbol.toStringTagFunction.nameについて十分に詳細に検討しました 。 要するに、内部のtoStringTagシンボルはモダン[[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 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の変数の型を判別するのは非常に簡単になりました。 この関数を使用すると、型の変数を一律に確認し、多態性関数でアヒルチェックの代わりに単純なスイッチ式を使用できます。 一般に、私はアヒルのタイピングに基づくアプローチが好きではありませんでした、彼らは何かがスプライス特性を持っている場合、それは配列か何かですか?


いくつかの間違ったアヒル


特定のメソッドまたはプロパティの存在によって変数のタイプを理解することは、もちろん全員のビジネスであり、状況に依存します。 しかし、このgetTagを使用して、いくつかの言語を調べます。


クラス?


JavaScriptで私のお気に入りの「アヒル」はクラスです。 ES-2015でJavaScriptを書き始めた人は、これらのクラスが何であるかを疑うことさえありません。 そして真実は:


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

クラスキーワード、コンストラクター、いくつかのメソッド、さらにextendsがあります。 また、 newを介してこのクラスのインスタンスを作成します。 通常の意味ではクラスのように見えます-クラスを意味します!


ただし、新しいメソッドをリアルタイムで 「クラス」に追加し始めると同時に、すでに作成されたインスタンスですぐに使用できるようになると、一部が失われます。


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

これをしないでください!


また、これはプロトタイプモデルに対する単なる構文上の砂糖であることを確実に知っている人もいません。なぜなら、言語では概念的には何も変わっていないからです。 PersonからgetTagを呼び出すと、作成された「クラス」ではなく「関数」を取得します。これは覚えておく価値があります。


その他の機能


JavaScriptで関数を宣言するには、 FunctionDeclarationFunctionExpression、および最近ArrowFunctionのいくつかの方法があります。 私たちは皆、いつ、何を使うべきかを知っています。物事はまったく異なります。 さらに、提案されたオプションのいずれかによって宣言された関数からgetTagを呼び出すと、 「Function」が取得されます。


実際、言語で関数を定義する方法は他にもたくさんあります。 少なくとも考慮されるClassDeclaration 、次に非同期関数とジェネレーターなどをリストに追加します: AsyncFunctionDeclarationAsyncFunctionExpressionAsyncArrowFunctionGeneratorDeclarationGeneratorExpressionClassExpressionMethodDefinition (リストは完全ではありません)。 そして、彼らはそう言っているようです? 上記はすべて関数のように動作します。つまり、 getTag"Function"を返します。 ただし、1つの機能があります。すべてのオプションは確かに関数ですが、すべてが直接ではない- 関数


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シンボルは、asinkとジェネレーター用にかなり再定義されています。 ただし、すべての関数のtypeof"function"を示します


インラインオブジェクト


SetMapDateErrorなどがあります。 それらにgetTagを適用すると、 「Function」が返されます。これらは、反復可能なコレクション、日付、およびエラーのコンストラクターであるためです。 インスタンスからそれぞれ「Set」「Map」「Date」 、「 Error 」を取得します。


しかし、気をつけて! JSONMathなどのオブジェクトがまだあります。 お急ぎの場合は、同様の状況を想定できます。 しかし、違います! これは完全に異なります-組み込みの単一オブジェクト。 それらはis not a constructorはありis not a constructortypeof呼び出しは、 「オブジェクト」 (誰が疑うか)を返します 。 ただし、 getTagtoStringTagに変わり、 「JSON」「Math」を取得します 。 これは私が共有したい最後の観察です。


狂信なしで行こう


最近、JavaScriptの変数のタイプについて、いつもよりも少し深く質問しました(動的なコード分析のために単純なオブジェクトインスペクターを作成することにしました)。 本番環境では必要ないため、マテリアルはAbnormal programmingで公開されるだけではありません。typeofinstanceofArray.isArrayisNaNなど、必要な検証を実行する際に覚えておくべきすべてのものがあります 。 ほとんどのプロジェクトには、TypeScript、Dart、またはFlowがあります。 私はジャバスクリプトが大好きです!



Source: https://habr.com/ru/post/J427253/


All Articles