ECMAScript 6では、クラスのtypeof
は、仕様によると'function'
。
ただし、仕様によれば、クラス構文で作成されたオブジェクトを通常の関数呼び出しとして呼び出すことはできません。つまり、new
キーワードを使用する必要があります。そうしないと、TypeErrorがスローされます。
TypeError: Classes can’t be function-called
したがって、非常に醜くパフォーマンスを破壊するtry catchを使用せずに、関数がclass
構文からのものか、function
構文からのものかを確認するにはどうすればよいですか?
関数がES6クラスかどうかを確認する最も簡単な方法は、 .toString()
メソッドの結果を確認することだと思います。 es2015 spec によると:
文字列表現には、オブジェクトの実際の特性に応じて、FunctionDeclaration FunctionExpression、GeneratorDeclaration、GeneratorExpression、ClassDeclaration、ClassExpression、ArrowFunction、MethodDefinition、またはGeneratorMethodの構文が必要です。
したがって、チェック関数は非常に単純に見えます。
function isClass(func) {
return typeof func === 'function'
&& /^class\s/.test(Function.prototype.toString.call(func));
}
いくつか調査したところ、ES6クラスのプロトタイプオブジェクト[ spec 19.1.2.16 ]はnon-writeable、非列挙型、非構成可能。
確認する方法は次のとおりです。
class F { }
console.log(Object.getOwnPropertyDescriptor(F, 'prototype'));
// {"value":{},"writable":false,"enumerable":false,"configurable":false
デフォルトの通常の関数はwriteable、non-enumerable、non-configurable。
function G() { }
console.log(Object.getOwnPropertyDescriptor(G, 'prototype'));
// {"value":{},"writable":true,"enumerable":false,"configurable":false}
ES6フィドル: http://www.es6fiddle.net/i7d0eyih/
したがって、ES6クラス記述子は常にこれらのプロパティをfalseに設定し、記述子を定義しようとするとエラーがスローされます。
// Throws Error
Object.defineProperty(F, 'prototype', {
writable: true
});
ただし、通常の関数を使用しても、これらの記述子を定義できます。
// Works
Object.defineProperty(G, 'prototype', {
writable: false
});
通常の関数で記述子が変更されることはあまり一般的ではないので、それを使用してクラスかどうかを確認できますが、これは実際の解決策ではありません。
@alexpodsの、関数を文字列化し、classキーワードをチェックする方法は、おそらく現時点で最良の解決策です。
このスレッドで言及されているさまざまなアプローチでいくつかの パフォーマンスベンチマーク を実行しました。概要は次のとおりです。
ネイティブクラス-Propsメソッド(大きな例では56倍、些細な例では15倍):
function isNativeClass (thing) {
return typeof thing === 'function' && thing.hasOwnProperty('prototype') && !thing.hasOwnProperty('arguments')
}
これは、次のことが当てはまるため機能します。
> Object.getOwnPropertyNames(class A {})
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} a (b,c) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(function () {})
[ 'length', 'name', 'arguments', 'caller', 'prototype' ]
> Object.getOwnPropertyNames(() => {})
> [ 'length', 'name' ]
ネイティブクラス-文字列メソッド(正規表現メソッドより約10%高速):
/**
* Is ES6+ class
* @param {any} value
* @returns {boolean}
*/
function isNativeClass (value /* :mixed */ ) /* :boolean */ {
return typeof value === 'function' && value.toString().indexOf('class') === 0
}
これは、従来のクラスを決定するためにも役立ちます。
// Character positions
const INDEX_OF_FUNCTION_NAME = 9 // "function X", X is at index 9
const FIRST_UPPERCASE_INDEX_IN_ASCII = 65 // A is at index 65 in ASCII
const LAST_UPPERCASE_INDEX_IN_ASCII = 90 // Z is at index 90 in ASCII
/**
* Is Conventional Class
* Looks for function with capital first letter MyClass
* First letter is the 9th character
* If changed, isClass must also be updated
* @param {any} value
* @returns {boolean}
*/
function isConventionalClass (value /* :any */ ) /* :boolean */ {
if ( typeof value !== 'function' ) return false
const c = value.toString().charCodeAt(INDEX_OF_FUNCTION_NAME)
return c >= FIRST_UPPERCASE_INDEX_IN_ASCII && c <= LAST_UPPERCASE_INDEX_IN_ASCII
}
上記のユースケースを含む typechecker
package をチェックすることもお勧めします-isNativeClass
メソッド、isConventionalClass
メソッド、および両方のタイプをチェックするisClass
メソッド。
Babel によって生成されたコンパイル済みコードを見ると、関数がクラスとして使用されているかどうかを判断する方法はないと思います。昔、JavaScriptにはクラスがなく、すべてのコンストラクタは単なる関数でした。今日のJavaScriptクラスキーワードは、「クラス」の新しい概念を導入していません。それは、むしろ構文糖衣です。
ES6コード:
_// ES6
class A{}
_
Babel によって生成されたES5:
_// ES5
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var A = function A() {
_classCallCheck(this, A);
};
_
もちろん、コーディング規約に慣れている場合は、関数(クラス)を解析して、その名前が大文字で始まっているかどうかを確認できます。
_function isClass(fn) {
return typeof fn === 'function' && /^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(fn);
}
_
編集:
すでにclassキーワードをサポートしているブラウザは、解析時にそれを使用できます。それ以外の場合は、大文字の1で行き詰まります。
編集:
Baluptonが指摘したように、Babelは匿名クラスに対してfunction _class() {}
を生成します。それに基づいて改良された正規表現。
編集:
__default
_のようなクラスを検出するために、正規表現に_export default class {}
_を追加しました
BabelJSは開発中のため、これらの場合にデフォルトの関数名が変更されないという保証はありません。本当に、それに頼るべきではありません。
New.targetを使用して、ES6クラス関数または関数コンストラクターによってインスタンス化されたかどうかを判断できます
class Person1 {
constructor(name) {
this.name = name;
console.log(new.target) // => // => [Class: Person1]
}
}
function Person2(){
this.name='cc'
console.log(new.target) // => [Function: Person2]
}
eS6を正しく理解している場合、class
を使用すると、入力している場合と同じ結果になります。
_var Foo = function(){}
var Bar = function(){
Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
_
_keyword new
_なしでMyClass()
を入力したときの構文エラーは、オブジェクトが使用することを目的とした変数でグローバルスペースを汚染するのを防ぐためです。
_var MyClass = function(){this.$ = "my private dollar"; return this;}
_
あなたが持っている場合
_// $ === jquery
var myObject = new MyClass();
// $ === still jquery
// myObject === global object
_
もしそうなら
_var myObject = MyClass();
// $ === "My private dollar"
_
関数として呼び出されたコンストラクターのthis
はグローバルオブジェクトを参照しますが、キーワードnew
を指定して呼び出されると、JavaScriptは最初に新しい空のオブジェクトを作成し、それからコンストラクターを呼び出します。
直接関係はありませんが、クラス、コンストラクター、または関数がユーザーによって生成され、関数を呼び出すか、新しいキーワードを使用してオブジェクトをインスタンス化する必要があるかを知りたい場合は、コンストラクタまたはクラス。他の回答で述べられているメソッド(toString
など)を使用して、クラスを関数から確実に判別できます。ただし、コードがbabelを使用してトランスパイルされる場合、それは確かに問題になります。
より簡単にするために、次のコードを試すことができます-
class Foo{
constructor(){
this.someProp = 'Value';
}
}
Foo.prototype.isClass = true;
またはコンストラクタ関数を使用している場合-
function Foo(){
this.someProp = 'Value';
}
Foo.prototype.isClass = true;
そして、あなたはそれがクラスであるかどうかをプロトタイププロパティでチェックすることでチェックできます。
if(Foo.prototype.isClass){
//It's a class
}
このメソッドは、クラスまたは関数がユーザーによって作成されていない場合、明らかに機能しません。 React.js
はこのメソッドを使用して、React Component
はクラスコンポーネントまたは関数コンポーネントです。この回答は、Dan Abramovの ブログ投稿 からの引用です。