Это всего лишь гипотетический вопрос, т.е. он не представляет собой реальной проблемы. Это возникло в результате написания статьи о том, как классы работают в JavaScript, в которой были описаны некоторые странные последствия их внутреннего устройства, что заставило меня задуматься: «Интересно, можно ли это странное использование JavaScript выразить в TypeScript?»
Когда я создаю класс в JavaScript, по умолчанию он «наследуется» Object. Т.е. прототипом его прототипа будет прототип Object.
Таким образом, TypeScript предполагает, что все они присутствуют в объекте.
class Foo {}
function x(y: Foo) {
// Valid. `toString` doesn't exist on Foo, it exist on `Object`
return y.toString();
}
Object сам по себе ни от чего не наследуется, поэтому прототипом является null.
> Object.getPrototypeOf(Object.prototype)
null
Но цепочка прототипов изменяема, и именно так мы имитировали наследование до того, как было введено ключевое слово class, хотя это было для вставки базового класса; не удаляя ни одного. Но я мог бы порвать с Object, установив для прототипа прототипа значение null.
> const f = new Foo()
undefined
> f.toString()
'[object Object]'
> Object.setPrototypeOf(Foo.prototype, null)
[Object: null prototype] {}
> f.toString()
Uncaught TypeError: f.toString is not a function
Теперь у Foo нет базового класса. Однако интерфейс TypeScript включает в себя все свойства Object.
Могу ли я правильно смоделировать это поведение?
п.с. Если вы читаете это и думаете: «Эй, я мог бы сделать это за…», не делайте этого. Просто не надо.
@jcalz Сценарий немного другой, но проблема по сути та же, поэтому я бы сказал: да, это отвечает на мой вопрос; этот тип поведения не поддерживается ;) Чего я тоже ожидал.






См. microsoft/TypeScript#1108.
TypeScript не может моделировать такие типы. TypeScript действительно предполагает, что все типы объектов (включая автоматически упакованные примитивы ) наследуются от Object. Это предположение, хотя и неверно с технической точки зрения, на практике работает достаточно хорошо, поэтому TypeScript не получает много сообщений об ошибках. В microsoft/TypeScript#1108 есть запрос на поддержку null объектов-прототипов, но он был открыт уже давно и получил всего несколько десятков голосов, так что, похоже, на него нет большого спроса.
Как упоминалось в обсуждении, самое близкое к удалению методов Object.prototype из типа объекта в TypeScript — это сузить их до никогда:
type NotObject = Record<keyof Object, never>
function makeNotObject<T extends object>(obj: T) {
const ret = Object.create(null!);
Object.assign(ret, obj);
return ret as T & NotObject;
}
const o = makeNotObject({ a: 1, b: 2, c: 3 });
console.info(o.a.toFixed(2)) // "1.00"
o.toString(); // error!
//~~~~~~~~ <-- This expression is not callable.
Но TypeScript по-прежнему рассматривает это как подтип Object, а это противоположно тому, о чем вы говорите.
Обратите внимание: даже если бы типы объектов null-прототипа поддерживались, конкретный пример, который вы показываете, мог бы не поддерживаться, поскольку TypeScript также не моделирует мутации типа. Вы можете сузить тип существующего объекта, но не изменять его произвольно. Итак, поведение Object.setPrototypeOf() на входе невозможно представить.
В microsoft/TypeScript#22865 есть запрос на функцию, позволяющую разрешить такие вещи, но эта функция, похоже, пользуется еще меньшей поддержкой сообщества. Для прототипов гораздо лучше просто Object.create() объект с желаемым прототипом.
Это ms/TS#1108. TS никогда не намеревался моделировать такое поведение (по-видимому, оно достаточно редко встречается в реальном коде, чтобы не стать большой проблемой для разработчиков TS). Есть неуклюжие обходные пути, но по сути это недостающая функция. Это полностью решает вопрос? Если да, то напишу ответ или найду подходящий источник дублирования. Если нет, то чего мне не хватает?