web-dev-qa-db-ja.com

ECMAScript 6のクラスと関数の違いをどのように確認しますか?

ECMAScript 6では、クラスのtypeofは、仕様によると'function'

ただし、仕様によれば、クラス構文で作成されたオブジェクトを通常の関数呼び出しとして呼び出すことはできません。つまり、newキーワードを使用する必要があります。そうしないと、TypeErrorがスローされます。

TypeError: Classes can’t be function-called

したがって、非常に醜くパフォーマンスを破壊するtry catchを使用せずに、関数がclass構文からのものか、function構文からのものかを確認するにはどうすればよいですか?

35
Moncader

関数が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));
}
35
alexpods

いくつか調査したところ、ES6クラスのプロトタイプオブジェクト[ spec 19.1.2.16 ]はnon-writeable非列挙型非構成可能

確認する方法は次のとおりです。

class F { }

console.log(Object.getOwnPropertyDescriptor(F, 'prototype'));
// {"value":{},"writable":false,"enumerable":false,"configurable":false

デフォルトの通常の関数はwriteablenon-enumerablenon-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キーワードをチェックする方法は、おそらく現時点で最良の解決策です。

12
Miguel Mota

このスレッドで言及されているさまざまなアプローチでいくつかの パフォーマンスベンチマーク を実行しました。概要は次のとおりです。


ネイティブクラス-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メソッド。

10
balupton

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は開発中のため、これらの場合にデフォルトの関数名が変更されないという保証はありません。本当に、それに頼るべきではありません。

2
Tamas Hegedus

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]
}
1
vinay0079

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は最初に新しい空のオブジェクトを作成し、それからコンストラクターを呼び出します。

0
webduvet

直接関係はありませんが、クラス、コンストラクター、または関数がユーザーによって生成され、関数を呼び出すか、新しいキーワードを使用してオブジェクトをインスタンス化する必要があるかを知りたい場合は、コンストラクタまたはクラス。他の回答で述べられているメソッド(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の ブログ投稿 からの引用です。

0
noob