Как вы проверяете разницу между классом ECMAScript 6 и функцией?

В ECMAScript 6 typeof классов, согласно спецификации, 'function'.

Однако также в соответствии со спецификацией вы не можете вызывать объект, созданный с помощью синтаксиса класса, как обычный вызов функции. Другими словами, вы должны использовать ключевое слово new, иначе вызывается TypeError.

TypeError: Classes can’t be function-called

Итак, не используя try catch, который был бы очень уродливым и разрушал бы производительность, как вы можете проверить, была ли функция получена из синтаксиса class или из синтаксиса function?

Ответы

Ответ 1

Я думаю, что самый простой способ проверить, является ли эта функция классом 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));
}

Ответ 2

Я провел некоторое исследование и выяснил, что объект-прототип [ spec 19.1.2.16 ] классов ES6, по-видимому, не записывается, не перечислим, не настраиваем.

Здесь можно проверить:

class F { }

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

Регулярная функция по умолчанию является записываемой, неперечислимой, неконфигурируемой.

function G() { }

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

ES6 Fiddle: http://www.es6fiddle.net/i7d0eyih/

Таким образом, дескриптор класса ES6 всегда будет иметь эти свойства, заданные как false, и будет вызывать ошибку, если вы попытаетесь определить дескрипторы.

// Throws Error
Object.defineProperty(F, 'prototype', {
  writable: true
});

Однако с помощью обычной функции вы все равно можете определить эти дескрипторы.

// Works
Object.defineProperty(G, 'prototype', {
  writable: false
});

Не очень распространено, что дескрипторы модифицируются на регулярных функциях, поэтому вы, вероятно, можете использовать их для проверки того, является ли это классом или нет, но, конечно, это не настоящее решение.

Метод @alexpods ', который строит функцию и проверяет ключевое слово класса, вероятно, является лучшим решением на данный момент.

Ответ 3

Определите некоторые тесты производительности на разных подходах, упомянутых в этом потоке, вот обзор:


Нативный класс - метод реквизита (самый быстрый на 56x на больших примерах и 15x на тривиальных примерах):

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' ]

Собственный класс - метод String (быстрее, чем метод регулярного выражения примерно на 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 пакет, который включает в себя варианты использования выше - с помощью метода isNativeClass, isConventionalClass и метод isClass, который проверяет оба типа.

Ответ 4

Поскольку существующие ответы касаются этой проблемы с точки зрения ES5-среды, я подумал, что, возможно, стоит предложить ответ с точки зрения ES2015+; исходный вопрос не указывает, и сегодня многим людям больше не нужно переводить классы, что немного изменяет ситуацию.

В частности, я хотел бы отметить, что можно окончательно ответить на вопрос: "Может ли это значение быть построено?" По общему признанию, это обычно не полезно само по себе; те же фундаментальные проблемы продолжают существовать, если вам нужно знать, можно ли вызывать значение.

Является конструктивным?

Для начала я думаю, что нам нужно прояснить некоторую терминологию, потому что вопрос о том, является ли значение конструктором, может означать более одного:

  1. Буквально, имеет ли это значение слот [[construct]]? Если это так, то это возможно. Если это не так, это невозможно.
  2. Была ли построена эта функция? Мы можем произвести некоторые негативы: функции, которые невозможно построить, не предназначены для построения. Но мы не можем сказать (не прибегая к эвристическим проверкам), является ли функция, которая является конструктивной, не предназначена для использования в качестве конструктора.

Что делает 2 unanswerable, так это то, что функции, созданные с ключевым словом function являются как конструктивными, так и вызываемыми, но такие функции часто предназначены только для одной из этих целей. Как уже отмечали некоторые другие, 2 также является подозрительным вопросом - сродни тому, чтобы спросить "что думал автор, когда писал это?". Я не думаю, что AI еще есть :) Хотя в идеальном мире, возможно, все авторы зарезервировали PascalCase для конструкторов (см. Функцию isConventionalClass), на практике было бы необычно встречать ложные срабатывания/негативы с этим тестом.

Что касается первой версии этого вопроса, да, мы можем знать, является ли функция конструктивной. Очевидно, что нужно попытаться построить его. Это действительно не приемлемо, хотя, потому что мы не знаем, будет ли это иметь побочные эффекты - похоже, что мы ничего не знаем о характере функции, так как если бы мы это сделали, нам не нужна эта проверка). К счастью, есть способ построить конструктор, не создавая его:

const isConstructable = fn => {
  try {
    new new Proxy(fn, { construct: () => ({}) });
    return true;
  } catch (err) {
    return false;
  }
};

construct Proxy-обработчик может переопределять проксированные значения [[construct]], но он не может сделать конструктивное значение конструктивным. Таким образом, мы можем "высмеять экземпляр" ввода, чтобы проверить, не сработает ли это. Обратите внимание, что ловушка конструкции должна возвращать объект.

isConstructable(class {});                      // true
isConstructable(class {}.bind());               // true
isConstructable(function() {});                 // true
isConstructable(function() {}.bind());          // true
isConstructable(() => {});                      // false
isConstructable((() => {}).bind());             // false
isConstructable(async () => {});                // false
isConstructable(async function() {});           // false
isConstructable(function * () {});              // false
isConstructable({ foo() {} }.foo);              // false
isConstructable(URL);                           // true

Обратите внимание, что функции стрелок, асинхронные функции, генераторы и методы не являются двунаправленными в том, как декларации и выражения функции "устаревшие". Этим функциям не задан слот [[construct]] (я думаю, не многие понимают, что синтаксис "стенографический метод" делает что-то - его не только сахар).

Итак, чтобы напомнить, если ваш вопрос действительно "является это конструктивным", то приведенное выше является окончательным. К сожалению, ничего больше не будет.

Что-то можно назвать?

Нужно снова прояснить вопрос, потому что, если они были очень буквальными, на самом деле работает следующее испытание:

const isCallable = fn => typeof fn === 'function';

Это связано с тем, что ES в настоящее время не позволяет вам создавать функцию без слота [[call]] (ну, связанные функции не имеют прямое, но они прокси до функции, которая делает).

Это может показаться неверным, потому что конструкторы, созданные с помощью синтаксиса класса, если вы попытаетесь вызвать их вместо их построения. Однако они могут быть вызваны - это просто, что их слот [[call]] определяется как функция, которая бросает! Oy.

Мы можем доказать это, превратив нашу первую функцию в ее зеркальное отражение.

// Demonstration only, this function is useless:

const isCallable = fn => {
  try {
    new Proxy(fn, { apply: () => undefined })();
    return true;
  } catch (err) {
    return false;
  }
};

isCallable(() => {});                      // true
isCallable(function() {});                 // true
isCallable(class {});                      // ... true!

Такая функция не помогает, но я хотел показать эти результаты, чтобы сфокусировать характер проблемы. Причина, по которой мы не можем легко проверить, является ли функция "только новой", заключается в том, что ответ не моделируется в терминах "отсутствия вызова", как "не-новый" моделируется в терминах "отсутствия конструкции". То, что было интересно знать, похоронено в методе, который мы не можем наблюдать, кроме его оценки, поэтому все, что мы можем сделать, это использовать эвристические проверки как прокси-сервер для того, что мы действительно хотим знать.

Эвристические варианты

Мы можем начать с сужения неоднозначных случаев. Любая функция, которая не может быть построена, однозначно может быть вызвана в обоих смыслах: если typeof fn === 'function' но isConstructable(fn) === false, у нас есть функция только для вызова, такая как стрелка, генератор или метод.

Таким образом, четырьмя интересными примерами являются class {} и function() {} плюс связанные формы обоих. Все остальное, что мы можем сказать, можно назвать только вызываемым. Обратите внимание, что ни один из текущих ответов не упоминает связанные функции, но они создают серьезные проблемы для любой эвристической проверки.

Как указывает балуптон, наличие или отсутствие дескриптора свойства для свойства "вызывающего" может служить индикатором того, как была создана функция. Экзотический объект связанной функции не будет иметь этого собственного свойства, даже если функция, которую он обертывает, делает. Свойство будет существовать через наследование из Function.prototype, но это верно и для конструкторов классов.

Аналогично, toString для BFEO обычно начинает "функцию", даже если связанная функция была создана с классом. Теперь эвристика для обнаружения самих BFEO заключалась бы в том, чтобы узнать, начинается ли их имя "привязано", но, к сожалению, это тупик; он все еще ничего не говорит нам о том, что было связано - это непрозрачно для нас.

Однако если toString возвращает "класс" (который не будет истинным, например, для конструкторов DOM), это довольно солидный сигнал, который не может быть вызван.

Лучшее, что мы можем сделать, это что-то вроде этого:

const isDefinitelyCallable = fn =>
  typeof fn === 'function' &&
  !isConstructable(fn);

isDefinitelyCallable(class {});                      // false
isDefinitelyCallable(class {}.bind());               // false
isDefinitelyCallable(function() {});                 // false <-- callable
isDefinitelyCallable(function() {}.bind());          // false <-- callable
isDefinitelyCallable(() => {});                      // true
isDefinitelyCallable((() => {}).bind());             // true
isDefinitelyCallable(async () => {});                // true
isDefinitelyCallable(async function() {});           // true
isDefinitelyCallable(function * () {});              // true
isDefinitelyCallable({ foo() {} }.foo);              // true
isDefinitelyCallable(URL);                           // false

const isProbablyNotCallable = fn =>
  typeof fn !== 'function' ||
  fn.toString().startsWith('class') ||
  Boolean(
    fn.prototype &&
    !Object.getOwnPropertyDescriptor(fn, 'prototype').writable // or your fave
  );

isProbablyNotCallable(class {});                      // true
isProbablyNotCallable(class {}.bind());               // false <-- not callable
isProbablyNotCallable(function() {});                 // false
isProbablyNotCallable(function() {}.bind());          // false
isProbablyNotCallable(() => {});                      // false
isProbablyNotCallable((() => {}).bind());             // false
isProbablyNotCallable(async () => {});                // false
isProbablyNotCallable(async function() {});           // false
isProbablyNotCallable(function * () {});              // false
isProbablyNotCallable({ foo() {} }.foo);              // false
isProbablyNotCallable(URL);                           // true

Случаи со стрелками указывают, где мы получаем ответы, которые нам особенно не нравятся.

В функции isProbablyNotCallable последняя часть условия может быть заменена другими проверками из других ответов; Я выбрал Miguel Motas здесь, так как он работает с (большинством?) Конструкторами DOM, даже теми, которые были определены до классов ES. Но это действительно не имеет значения - у каждой возможной проверки есть недостаток, и нет никакой волшебной комбинации.


Вышеприведенное выше, насколько я знаю, является тем, что возможно и не возможно в современных ЭС. Он не отвечает потребностям, специфичным для ES5 и ранее, хотя на самом деле в ES5 и ранее ответ на оба вопроса всегда "истинен" для любой функции.

Будущее

Существует предложение для собственного теста, который сделает наблюдаемый слот [[FunctionKind]] наблюдаемым, если будет показано, была ли создана функция с class:

https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md

Если это предложение или что-то вроде этого продвинется, мы бы получили возможность решить эту проблему конкретно, когда дело доходит до class по крайней мере.

* Игнорирование дела приложения B [[IsHTMLDDA]].

Ответ 5

Глядя на скомпилированный код, созданный Babel, я думаю, что вы не можете сказать, используется ли функция как класс, В то время JavaScript не имел классов, и каждый конструктор был просто функцией. В настоящее время ключевое слово JavaScript не вводит новую концепцию "классов", это скорее синтаксический сахар.

Код ES6:

// ES6
class A{}

ES5, сгенерированный Babel:

// 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);
}

EDIT:

Браузеры, которые уже поддерживают ключевое слово класса, могут использовать его при разборе. В противном случае вы застряли с большой буквы.

EDIT:

Как отметил Балуптон, Бабель генерирует function _class() {} для анонимных классов. На этом улучшено регулярное выражение.

EDIT:

Добавлен _default в регулярное выражение, чтобы обнаружить такие классы, как export default class {}

Предупреждение

BabelJS находится в разработке, и нет гарантии, что они не изменят имена функций по умолчанию в этих случаях. На самом деле, вы не должны полагаться на это.

Ответ 6

если я правильно понял ES6, используя class, имеет тот же эффект, что и при вводе

var Foo = function(){}
var Bar = function(){
 Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;

синтаксическая ошибка при вводе MyClass() без keyword new заключается в том, чтобы предотвратить загрязнение глобального пространства переменными, предназначенными для использования объектом.

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 сначала создает новый пустой объект, а затем вызывает на нем конструктор.

Ответ 7

Хотя это не связано напрямую, но если класс, конструктор или функция генерируется вами, и вы хотите знать, следует ли вам вызывать функцию или создавать экземпляр объекта с помощью нового ключевого слова, вы можете сделать это, добавив пользовательский флаг в прототип конструктор или класс. Вы можете, конечно, сказать класс из функции, используя методы, упомянутые в других ответах (например, 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 a class
}

Этот метод, очевидно, не будет работать, если класс или функция не созданы вами. React.js использует этот метод для проверки того, является ли React Component класса или функциональным компонентом. Этот ответ взят из сообщения блога Дэна Абрамова