Как проверить, является ли переменная объявлением класса ES6?

Я экспортирую следующий класс ES6 из одного модуля:

export class Thingy {
  hello() {
    console.log("A");
  }

  world() {
    console.log("B");
  }
}

И импортируя его из другого модуля:

import {Thingy} from "thingy";

if (isClass(Thingy)) {
  // Do something...
}

Как проверить, является ли переменная классом? Не экземпляр класса, а объявление класса?

Другими словами, как реализовать функцию isClass в приведенном выше примере?

Ответ 1

Я проясню это здесь, любая произвольная функция может быть конструктором. Если вы различаете "класс" и "функцию", вы делаете неправильный выбор дизайна API. Если вы предполагаете, что что-то должно быть class, например, никто не использует Babel или Typescript, будет обнаружен как class, потому что вместо этого код будет преобразован в функцию. Это означает, что вы требуете, чтобы кто-либо, использующий вашу кодовую базу, должен работать в среде ES6 в целом, поэтому ваш код будет непригоден для старых сред.

Ваши параметры здесь ограничены поведением, определяемым реализацией. В ES6, когда код разобран и синтаксис обрабатывается, осталось мало изменений в классе. Все, что у вас есть, - это функция-конструктор. Ваш лучший выбор - сделать

if (typeof Thingy === 'function'){
  // It a function, so it definitely can't be an instance.
} else {
  // It could be anything other than a constructor
}

и если кто-то должен выполнить функцию не-конструктора, выведите для него отдельный API.

Очевидно, что это не тот ответ, который вы ищете, но важно сделать это понятным.

Как упоминается здесь в другом ответе, у вас есть опция, потому что для возврата декларации класса требуется .toString() для функций, например.

class Foo {}
Foo.toString() === "class Foo {}" // true

Главное, однако, в том, что это применимо только в том случае, если это возможно. Это соответствует 100% -ной спецификации для реализации,

class Foo{}
Foo.toString() === "throw SyntaxError();"

В настоящее время нет браузеров, но есть несколько встроенных систем, которые сосредоточены на JS-программировании, например, и для сохранения памяти для самой вашей программы, они отбрасывают исходный код после его анализа, то есть у них не будет исходного кода для возврата из .toString(), и это разрешено.

Аналогично, используя .toString(), вы делаете предположения как о будущей проверке, так и об общем дизайне API. Скажите, что вы делаете

const isClass = fn => /^\sclass/.test(fn.toString());

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

Возьмите декораторы, например:

@decorator class Foo {}
Foo.toString() == ???

Входит ли .toString() из этого декоратора? Что, если сам декоратор возвращает function вместо класса?

Ответ 2

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

function isClass(v) {
  return typeof v === 'function' && /^\s*class\s+/.test(v.toString());
}

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

function isClass(v) {
  if (typeof v !== 'function') {
    return false;
  }
  try {
    v();
    return false;
  } catch(error) {
    if (/^Class constructor/.test(error.message)) {
      return true;
    }
    return false;
  }
}

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

Ответ 3

Возможно, это поможет

let is_class = (obj) => {
    try {
        new obj();
        return true;
    } catch(e) {
        return false;
    };
};

Ответ 4

Как насчет:

function isClass(v) {
   return typeof v === 'function' && v.prototype.constructor === v;
}