Наследовать класс ES6/TS из неклассов

Учитывая, что класс расширен из неклассического (включая, но не ограничиваясь, функцию),

function Fn() {} 

class Class extends Fn {
    constructor() {
        super();
    }
}

каковы последствия? Что говорят об этом спецификации?

Похоже, что текущие реализации Babel, Google V8 и Mozilla Spidermonkey в порядке с этим, и TypeScript throws

Тип '() = > void' не является типом функции конструктора

Если это действительный код ES2015, какой правильный способ обработать его в TypeScript?

Ответы

Ответ 1

TypeScript Часть

До сих пор spec говорит, что за тегом extends должен следовать TypeReference. И TypeReference должен быть в форме A.B.C<TypeArgument>, например MyModule.MyContainer<MyItem>. Итак, синтаксически ваш код прав. Но это не типичный случай.

В спецификации указано, что BaseClass должен быть допустимым классом typescript. Однако спецификация устарела, как сказано в здесь. Теперь typescript допускает выражения в выражении extends, если выражения вычисляются функцией-конструктором. Это определение, на самом деле, основано на реализации. Вы можете увидеть здесь здесь. Проще говоря, выражение можно считать конструктором, если оно реализует интерфейс new() {}.

ES2015 Часть

Итак, ваша проблема - простая функция, которая не распознается как конструктор в TypeScript, что можно утверждать, потому что спецификация ES2015 требует, чтобы объект имел внутренний метод [[construct]]. Хотя пользовательский объект функции имеет его.

ES2015 требует BaseClass является конструктором в во время выполнения. Объект isConstructor, если он имеет [[construct]] внутренний код. Спектр говорит, что [[construct]] является внутренним методом для Объекта функции. Пользовательские функции являются экземплярами Function Objects, поэтому, естественно, они являются конструкторами. Но встроенная функция и функция стрелки может не иметь [[construct]].

Например, следующий код будет вызывать время выполнения TypeError, потому что parseInt является встроенной функцией и не имеет [[construct]]

new parseInt()
// TypeError: parseInt is not a constructor

И из ECMAScript

Функции стрелок похожи на встроенные функции, поскольку оба они не имеют .prototype и любой внутренний метод [[Construct]]. Таким образом, new (() = > {}) выдает TypeError, но в противном случае стрелки похожи на функции:

Как правило, любая функция без prototype не new -able.

Работа вокруг

Короче говоря, не каждая функция является конструктором, typescript захватывает это, требуя new() {}. Однако определяемая пользователем функция является конструктором.

Чтобы обойти это, самый простой способ - объявить Fn как переменную и вставить его в конструктор.

interface FnType {}
var Fn: {new(): FnType} = (function() {}) as any

class B extends Fn {}

Рассуждение несовместимости

DISCALIMER: Я не основной разработчик typescript, а просто поклонник TS, у которого есть несколько побочных проектов, связанных с TS. Итак, этот раздел - моя личная догадка.

TypeScript - проект, созданный в 2012, когда ES2015 все еще надвигался в темноте. typescript не имел хорошей ссылки на семантику класса.

В то время главная цель typescript заключалась в том, чтобы поддерживать совместимость с ES3/5. Таким образом, new функция является законной в TypeScript, поскольку она также является законной в ES3/5. В то же время typescript также нацелено на захват ошибок программирования. extends функция может быть ошибкой, потому что функция может быть не разумным конструктором (скажем, функцией исключительно для побочного эффекта). extends даже не существовало в ES3/5! Таким образом, typescript может свободно определять свое использование extends, поэтому extends должен быть сопряжен с переменной class. Это сделало typescript больше TypeSafe, будучи совместимым с JavaScript.

Теперь спецификация ES2015 завершена. JavaScript также имеет ключевое слово extends! Тогда возникает несовместимость. Для устранения несовместимости существуют усилия. Однако все еще существуют проблемы. Тип () => void или Function не должен расширяться, как указано выше из-за встроенной функции. Следующий код сломается

var a: (x: string) => void = eval
new a('booom')

С другой стороны, если a ConstructorInterface был введен в typescript, и каждый реализованный им литерал функции, то возникла бы обратная несовместимость. Следующий код компилируется сейчас, но не тогда, когда был введен ConstructorInterface

var a = function (s) {}
a = parseInt // compile error because parseInt is not assignable to constructor

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

Ответ 2

каковы последствия? Что говорят об этом спецификации?

Это то же самое, что расширение класса "EcmaScript 5". Вы объявляете конструкторную функцию и вообще не имеете прототипа. Вы можете расширить его без каких-либо проблем.

Но для TypeScript существует большая разница между function Fn() {} и class Fn {}. Оба они не одного типа.

Первая - это просто функция, которая ничего не возвращает (и TypeScript показывает ее с помощью () => void). Второй - это функция-конструктор. TypeScript отказывается выполнять расширение для функции-не-конструктора.

Если в один прекрасный день javascript откажется это сделать, он сломает многие коды javascript. Потому что на данный момент function Fn() {} - наиболее используемый способ объявить класс в чистом javascript. Но с точки зрения TypeScript это не "безопасный тип".

Я думаю, что единственный способ для TypeScript - использовать класс:

class Fn {} 

class Class extends Fn {
    constructor() {
        super();
    }
}

Ответ 3

Я не уверен, что отвечу на ваш вопрос, но мне было очень интересно, как расширить JS-функцию классом TypeScript, поэтому я пробовал следующее:

fn.js (обратите внимание на расширение .js!)

function Fn() {
    console.log("2");
}

c.ts

declare class Fn {}

class C extends Fn {
    constructor() {
        console.log("1");
        super();
        console.log("3");
    }
}

let c = new C();
c.sayHello();

Затем я побежал:

$ tsc --target es5 c.ts | cat fn.js c.js | node  # or es6

а выход:

1
2
3
Hello!

Обратите внимание, что это не код для производственного использования, а скорее обходной путь для случаев, когда у вас нет времени преобразовать какой-то старый JS файл в TypeScript.

Если бы я был в ситуации OP, я бы попытался преобразовать Fn в класс, потому что он делает код проще для других в команде.

Ответ 4

Это косвенно связано с интерфейсом TypeScript для описания класса

Хотя класс typeof a function, в Typescript нет класса (или интерфейса), который является суперклассом всех классов.

однако все сопряжены с помощью function

Я предполагаю, что совместим с Javascript, function должен быть, вероятно, классом, все функции должны быть этого класса, и все классы должны его расширять...