TypeScript— это бесплатный язык программирования с открытым исходным кодом, разработанный Microsoft. Это расширенный набор JavaScript, который, по сути, добавляет к языку необязательную статическую типизацию и объектно-ориентированное программирование на основе классов.
TypeScript предоставляет новейшие и развивающиеся функции JavaScript, в том числе из ECMAScript 2015 и будущих предложений, таких как асинхронные функции и декораторы, для помощи в создании надежных компонентов.
В этой статье брат Абао поделится некоторыми проблемами TS, которые возникали в процессе изучения TypeScript на протяжении многих лет.Я надеюсь, что эта статья может быть полезна небольшим партнерам, изучающим TypeScript. Начнем с первой проблемы — как явно установить свойства объекта окна.
1. Как явно установить свойства объекта окна
Для разработчиков, которые использовали JavaScript, дляwindow.MyNamespace = window.MyNamespace || {};
Эта строка кода не является незнакомой. Чтобы избежать конфликтов во время разработки, мы обычно устанавливаем отдельные пространства имен для определенных функций.
Однако в ТС дляwindow.MyNamespace = window.MyNamespace || {};
Для этой строки кода компилятор TS запросит следующую информацию об исключении:
Property 'MyNamespace' does not exist on type 'Window & typeof globalThis'.(2339)
Приведенная выше информация об исключении заключается в том, что вWindow & typeof globalThis
не существует на кроссовом типеMyNamespace
Атрибуты. Итак, как решить эту проблему? Самый простой способ - использовать утверждение типа:
(window as any).MyNamespace = {};
Хотя с помощьюanyДафа может решить эти проблемы, но лучший способ — расширитьlib.dom.d.ts
в файлеWindow
Интерфейс для решения вышеуказанных проблем, конкретные методы заключаются в следующем:
declare interface Window {
MyNamespace: any;
}
window.MyNamespace = window.MyNamespace || {};
Давайте посмотрим на это сноваlib.dom.d.ts
объявлено в файлеWindow
интерфейс:
/**
* A window containing a DOM document; the document property
* points to the DOM document loaded in that window.
*/
interface Window extends EventTarget, AnimationFrameProvider, GlobalEventHandlers,
WindowEventHandlers, WindowLocalStorage, WindowOrWorkerGlobalScope, WindowSessionStorage {
// 已省略大部分内容
readonly devicePixelRatio: number;
readonly document: Document;
readonly top: Window;
readonly window: Window & typeof globalThis;
addEventListener(type: string, listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof WindowEventMap>(type: K,
listener: (this: Window, ev: WindowEventMap[K]) => any,
options?: boolean | EventListenerOptions): void;
[index: number]: Window;
}
Выше мы объявили два одинаковых имениWindow
интерфейс, в это время конфликтов не будет. TypeScript автоматически объединит интерфейсы, то есть поместит членов обеих сторон в интерфейс с одинаковым именем.
2. Как динамически назначать свойства объектам
В JavaScript мы можем легко динамически назначать свойства объектам, например:
let developer = {};
developer.name = "semlinker";
Приведенный выше код отлично работает в JavaScript, но в TypeScript компилятор выдаст следующее исключение:
Property 'name' does not exist on type '{}'.(2339)
{}
Тип представляет объект без членов, поэтому тип не содержитname
Атрибуты. Чтобы решить эту проблему, мы можем объявитьLooseObject
тип:
interface LooseObject {
[key: string]: any
}
Этот тип используетподпись индексаописание формыLooseObject
Тип может принимать тип ключа — строку, а тип значения — поле любого типа. имеютLooseObject
После типа мы можем решить вышеуказанную проблему следующими способами:
interface LooseObject {
[key: string]: any
}
let developer: LooseObject = {};
developer.name = "semlinker";
заLooseObject
С точки зрения типа, его ограничения очень свободны. В некоторых сценариях приложений, помимо поддержки динамических атрибутов, мы также надеемся объявить некоторые обязательные и необязательные атрибуты.
Например, для интерфейса разработчика, представляющего разработчика, мы хотим, чтобы его атрибут name был обязательным, а атрибут age — необязательным, а также поддержка динамического задания атрибутов строкового типа. Мы можем сделать это для этого требования:
interface Developer {
name: string;
age?: number;
[key: string]: any
}
let developer: Developer = { name: "semlinker" };
developer.age = 30;
developer.city = "XiaMen";
На самом деле, помимо использованияподпись индексаКроме того, мы также можем использовать встроенные типы инструментов TypeScript.Record
чтобы определить интерфейс разработчика:
// type Record<K extends string | number | symbol, T> = { [P in K]: T; }
interface Developer extends Record<string, any> {
name: string;
age?: number;
}
let developer: Developer = { name: "semlinker" };
developer.age = 30;
developer.city = "XiaMen";
В-третьих, как понять общий<T>
Для читателей, плохо знакомых с дженериками TypeScript, первое<T>
Синтаксис покажется вам незнакомым. На самом деле ничего особенного, как и при передаче параметров, мы передаем тип, который хотим использовать для вызова конкретной функции.
Ссылаясь на картинку выше, когда мы вызываем identity<Number>(1)
,Number
Типы похожи на параметры1
то же самое появитсяT
заполняет тип в любом месте. на фото<T>
ВнутреннийT
называется переменной типа, это заполнитель для типа, который мы хотим передать функции идентификации, и он назначаетсяvalue
Параметр используется вместо его типа: в настоящее времяT
Действует как тип, а не как конкретный числовой тип.
вT
представлятьType, обычно используемое в качестве имени переменной первого типа при определении универсального. Но на самом делеT
Можно заменить любым допустимым именем. КромеT
Кроме того, ниже приведены значения общих общих переменных:
- K (ключ): представляет тип ключа в объекте;
- V (значение): представляет тип значения в объекте;
- E (элемент): указывает тип элемента.
На самом деле, вместо того, чтобы определять только одну переменную типа, мы можем ввести любое количество переменных типа, которые мы хотим определить. Например, мы вводим переменную нового типаU
, который используется для расширения нашего определенногоidentity
функция:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
Вместо того, чтобы явно задавать значения для переменных типа, более распространенной практикой является автоматический выбор компилятором этих типов, что делает код чище. Мы можем полностью опустить угловые скобки, например:
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
Для приведенного выше кода компилятор достаточно умен, чтобы знать наши типы параметров и назначать их T и U без явного указания их разработчиком.
В-четвертых, как понять роль декораторов
В TypeScript декораторы делятся на четыре категории: декораторы классов, декораторы свойств, декораторы методов и декораторы параметров. Суть декоратора — это функция, с помощью которой мы можем легко определить метаданные, относящиеся к объекту.
например, вionic-nativeпроект, он используетPlugin
Decorator для определения информации о плагине Device в IonicNative:
@Plugin({
pluginName: 'Device',
plugin: 'cordova-plugin-device',
pluginRef: 'device',
repo: 'https://github.com/apache/cordova-plugin-device',
platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],
})
@Injectable()
export class Device extends IonicNativePlugin {}
В приведенном выше коде функция Plugin называется фабрикой декораторов. После вызова этой функции она вернет декоратор класса, который используется для декорирования класса Device. Функция фабрики плагинов определяется следующим образом:
// https://github.com/ionic-team/ionic-native/blob/v3.x/src/%40ionic-native/core/decorators.ts
export function Plugin(config: PluginConfig): ClassDecorator {
return function(cls: any) {
// 把config对象中属性,作为静态属性添加到cls类上
for (let prop in config) {
cls[prop] = config[prop];
}
cls['installed'] = function(printWarning?: boolean) {
return !!getPlugin(config.pluginRef);
};
// 省略其他内容
return cls;
};
}
Наблюдая за сигнатурой метода фабричной функции плагина, мы можем знать, что после вызова функции она вернетClassDecorator
объект типа, гдеClassDecorator
Объявление типа выглядит так:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction)
=> TFunction | void;
Декораторы классов, как следует из названия, используются для оформления классов. Он принимает один параметр -target: TFunction
, представляющий украшаемый класс. После введения вышеизложенного, давайте рассмотрим еще один вопрос@Plugin({...})
середина@
Для чего нужны символы?
фактически@Plugin({...})
середина@
Символы — это просто синтаксический сахар, почему именно синтаксический сахар? Здесь мы смотрим на скомпилированный код ES5:
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var Device = /** @class */ (function (_super) {
__extends(Device, _super);
function Device() {
return _super !== null && _super.apply(this, arguments) || this;
}
Device = __decorate([
Plugin({
pluginName: 'Device',
plugin: 'cordova-plugin-device',
pluginRef: 'device',
repo: 'https://github.com/apache/cordova-plugin-device',
platforms: ['Android', 'Browser', 'iOS', 'macOS', 'Windows'],
}),
Injectable()
], Device);
return Device;
}(IonicNativePlugin));
Из сгенерированного кода видно, что@Plugin({...})
и@Injectable()
со временем будут преобразованы в обычные вызовы методов, а результаты их вызовов в итоге будут переданы в качестве аргументов в виде массивов в__decorate
функция, в то время как__decorate
Внутри функции будетDevice
Классы вызывают соответствующие декораторы типов в качестве аргументов, тем самым расширяя соответствующие функциональные возможности.
Кроме того, если вы использовали Angular, я думаю, вы знакомы со следующим кодом.
const API_URL = new InjectionToken('apiUrl');
@Injectable()
export class HttpService {
constructor(
private httpClient: HttpClient,
@Inject(API_URL) private apiUrl: string
) {}
}
существуетInjectable
декоратор классаHttpService
В классе внедряем метод обработки HTTP-запросов, построив инъекциюHttpClient
Зависимый объект. и черезInject
Вводится декоратор параметровAPI_URL
Соответствующий объект, таким образом, мы называем его Dependency Injection.
Относительно того, что такое внедрение зависимостей и как реализовать функцию внедрения зависимостей в TS, для экономии места Brother Abao не будет продолжать расширяться здесь. Заинтересованные друзья могут прочитать »Потрясающие IoC и DI" Эта статья.
Пять, как понять роль перегрузки функций
5.1 Прекрасные и ненавистные типы союзов
Поскольку JavaScript — динамический язык, мы обычно вызываем одну и ту же функцию с разными типами параметров, и функция будет возвращать разные типы результатов вызова в соответствии с разными параметрами:
function add(x, y) {
return x + y;
}
add(1, 2); // 3
add("1", "2"); //"12"
Поскольку TypeScript является надмножеством JavaScript, приведенный выше код можно использовать непосредственно в TypeScript, но при включенном компиляторе TypeScriptnoImplicitAny
Когда элемент конфигурации , приведенный выше код вызовет следующее сообщение об ошибке:
Parameter 'x' implicitly has an 'any' type.
Parameter 'y' implicitly has an 'any' type.
Эта информация говорит нам, что параметр x и параметр y неявно имеютany
тип. Чтобы решить эту проблему, мы можем установить тип параметра. потому что мы надеемсяadd
Функция поддерживает как строковые, так и числовые типы, поэтому мы можем определитьstring | number
Тип объединения, и мы берем псевдоним для типа объединения:
type Combinable = string | number;
После определения типа объединения Combinable обновим егоadd
функция:
function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
заadd
После явного ввода параметров функции предыдущее сообщение об ошибке исчезает. Тогда в это времяadd
Функция идеальна, давайте на самом деле протестируем ее:
const result = add('semlinker', ' kakuqo');
result.split(' ');
В приведенном выше коде мы используем'semlinker'
и' kakuqo'
Вызовите функцию добавления с этими двумя строками в качестве аргументов и сохраните результат вызова в файл с именемresult
В настоящее время мы считаем само собой разумеющимся, что тип результирующей переменной — строка, поэтому мы можем вызывать строковый объект обычным образом.split
метод. Но затем компилятор TypeScript выдал следующее сообщение об ошибке:
Property 'split' does not exist on type 'Combinable'.
Property 'split' does not exist on type 'number'.
Это понятноCombinable
иnumber
не существует для объекта типаsplit
Атрибуты. Проблема возникает снова, как ее решить? На этом этапе мы можем воспользоваться перегрузкой функций, предоставляемой TypeScript.
5.2 Перегрузка функций
Перегрузка функций или перегрузка методов — это возможность создавать несколько методов с одинаковыми именами и разным количеством или типами параметров.
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
// type Combinable = string | number;
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
В приведенном выше коде мы предоставляем определения нескольких типов функций для функции добавления для реализации перегрузки функций. В TypeScript, помимо перегрузки обычных функций, мы также можем перегружать методы-члены в классах.
Перегрузка метода означает, что метод имеет одно и то же имя и разные параметры в одном и том же классе (другой тип параметра, другое количество параметров или другой порядок параметров при одинаковом количестве параметров). которым метод выполняет действие. Поэтому условия перегрузки методов-членов в классе таковы: в одном классе имена методов одинаковы, а списки параметров разные. Давайте рассмотрим пример перегрузки метода-члена:
class Calculator {
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: string, b: number): string;
add(a: number, b: string): string;
add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
}
const calculator = new Calculator();
const result = calculator.add('Semlinker', ' Kakuqo');
Предупреждение здесь заключается в том, что когда компилятор TypeScript обрабатывает перегрузку функций, он просматривает список перегрузок и пытается использовать первое определение перегрузки. Используйте это, если оно совпадает. Поэтому при определении перегрузок обязательно сначала укажите наиболее точное определение. Также в классе «Калькулятор»add(a: Combinable, b: Combinable){ }
Не является частью перегруженного списка, поэтому для метода добавления члена мы определяем только четыре перегруженных метода.
6. В чем разница между интерфейсами и типами
6.1 Objects/Functions
И интерфейсы, и псевдонимы типов могут использоваться для описания формы объекта или сигнатуры функции:
интерфейс
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
введите псевдоним
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
6.2 Other Types
В отличие от интерфейсных типов, псевдонимы типов могут использоваться для некоторых других типов, таких как примитивные типы, типы объединения и кортежи:
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
6.3 Extend
И интерфейсы, и псевдонимы типов могут быть расширены, но синтаксис отличается. Кроме того, интерфейсы и псевдонимы типов не исключают друг друга. Интерфейсы могут расширять псевдонимы типов, но не наоборот.
Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX {
y: number;
}
Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
6.4 Implements
Классы могут реализовывать интерфейсы или псевдонимы типов таким же образом, но классы не могут реализовывать типы объединения, определенные с помощью псевдонимов типов:
interface Point {
x: number;
y: number;
}
class SomePoint implements Point {
x = 1;
y = 2;
}
type Point2 = {
x: number;
y: number;
};
class SomePoint2 implements Point2 {
x = 1;
y = 2;
}
type PartialPoint = { x: number; } | { y: number; };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint { // Error
x = 1;
y = 2;
}
6.5 Declaration merging
В отличие от псевдонимов типов, интерфейсы можно определять несколько раз, и они автоматически объединяются в один интерфейс.
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
7. В чем разница между объектом, объектом и {}
7.1 тип объекта
Тип объекта: новый тип, представленный в TypeScript 2.2, который используется для представления непримитивных типов.
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
create(o: object | null): any;
// ...
}
const proto = {};
Object.create(proto); // OK
Object.create(null); // OK
Object.create(undefined); // Error
Object.create(1337); // Error
Object.create(true); // Error
Object.create("oops"); // Error
7.2 Тип объекта
Тип объекта: это тип всех экземпляров класса Object, который определяется следующими двумя интерфейсами:
- Интерфейс Object определяет свойства объекта-прототипа Object.prototype;
// node_modules/typescript/lib/lib.es5.d.ts
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
- Интерфейс ObjectConstructor определяет свойства класса Object.
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;
readonly prototype: Object;
getPrototypeOf(o: any): any;
// ···
}
declare var Object: ObjectConstructor;
Все экземпляры класса Object наследуют все свойства интерфейса Object.
7.3 Типы {}
Тип {} описывает объект без членов. TypeScript сгенерирует ошибку времени компиляции, когда вы попытаетесь получить доступ к произвольным свойствам такого объекта.
// Type {}
const obj = {};
// Error: Property 'prop' does not exist on type '{}'.
obj.prop = "semlinker";
Однако вы по-прежнему можете использовать все свойства и методы, определенные для типа Object, которые неявно доступны через цепочку прототипов JavaScript:
// Type {}
const obj = {};
// "[object Object]"
obj.toString();
8. В чем разница между числовым перечислением и строковым перечислением
8.1 Цифровое перечисление
В JavaScript логические переменные имеют ограниченный диапазон значений, т.е.true
иfalse
. А с перечислениями в TypeScript вы также можете настраивать похожие типы:
enum NoYes {
No,
Yes,
}
No
иYes
известный как перечислениеNoYes
член. Каждый член перечисления имеет имя и значение. Типом значения члена числового перечисления по умолчанию является числовой тип. То есть значение каждого члена является числом:
enum NoYes {
No,
Yes,
}
assert.equal(NoYes.No, 0);
assert.equal(NoYes.Yes, 1);
Вместо того, чтобы позволить TypeScript указать нам значения членов перечисления, мы можем присвоить значения вручную:
enum NoYes {
No = 0,
Yes = 1,
}
Это явное присваивание через знак равенства называетсяinitializer
. Если значение члена перечисления назначено явно, но последующие члены не присваивают значения явно, TypeScript добавит 1 к значению последующего члена на основе значения текущего члена.
8.2 Перечисление строк
В дополнение к числовым перечислениям мы также можем использовать строки в качестве значений элементов перечисления:
enum NoYes {
No = 'No',
Yes = 'Yes',
}
assert.equal(NoYes.No, 'No');
assert.equal(NoYes.Yes, 'Yes');
8.3. Числовое перечисление против строкового перечисления
В чем разница между числовым перечислением и строковым перечислением? Здесь мы взглянем на результаты компиляции числового перечисления и строкового перечисления соответственно:
Результат составления числового перечисления
"use strict";
var NoYes;
(function (NoYes) {
NoYes[NoYes["No"] = 0] = "No";
NoYes[NoYes["Yes"] = 1] = "Yes";
})(NoYes || (NoYes = {}));
Результат компиляции перечисления строк
"use strict";
var NoYes;
(function (NoYes) {
NoYes["No"] = "No";
NoYes["Yes"] = "Yes";
})(NoYes || (NoYes = {}));
Наблюдая за приведенными выше результатами, мы знаем, что числовое перечисление поддерживает в дополнение кОт имени элемента к значению элементаВ дополнение к нормальному отображению , он также поддерживаетОт значения члена к имени членаобратное отображение. Кроме того, для чисто строковых перечислений мы не можем пропустить какие-либо инициализаторы. Числовые перечисления инициализируются значениями по умолчанию, если никакое значение не задано явно.
8.4 Присвоение значений за пределами числовых перечислений
Говоря о цифровом перечислении, давайте рассмотрим еще один вопрос:
const enum Fonum {
a = 1,
b = 2
}
let value: Fonum = 12; // Ok
Я думаю, многие читатели увидятlet value: Fonum = 12;
Удивительно, что компилятор TS не выдает никаких ошибок в этой строке. Очевидно, что число 12 не входит в перечисление Фонума. Почему это так? Давайте взглянемTypeScript issues 26362серединаDanielRosenwasserОтвет большого парня:
The behavior is motivated by bitwise operations. There are times when SomeFlag.Foo | SomeFlag.Bar is intended to produce another SomeFlag. Instead you end up with number, and you don't want to have to cast back to SomeFlag.
Поведение вызвано побитовыми операциями. Иногда SomeFlag.Foo | SomeFlag.Bar используется для генерации другого SomeFlag. Вместо этого вы получаете числа, и вы не хотите принудительно переходить на SomeFlag.
Разобравшись с вышеизложенным, давайте посмотримlet value: Fonum = 12;
Это утверждение компилятор TS не сообщит об ошибке, потому что число 12 может быть вычислено из существующих элементов перечисления Fonum.
let value: Fonum =
Fonum.a << Fonum.b << Fonum.a | Fonum.a << Fonum.b; // 12
9. Используйте#
Определенные частные поля сprivate
В чем разница между полями определения модификатора
Поддерживается начиная с TypeScript 3.8Частные поля ECMAScript, использовать следующим образом:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
let semlinker = new Person("Semlinker");
semlinker.#name;
// ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
с обычными свойствами (даже с использованиемprivate
свойства, объявленные модификатором), приватные поля учитывают следующие правила:
- Частные поля начинаются с
#
характер, иногда мы называем его частным именем; - Каждое имя частного поля уникально квалифицировано для содержащего его класса;
- Модификаторы доступности TypeScript (например, public или private) нельзя использовать в закрытых полях;
- К закрытым полям нельзя получить доступ за пределами содержащего класса или даже обнаружить.
Говоря об использовании#
Определенные частные поля сprivate
В чем разница между полями определения модификатора? Теперь давайте посмотрим на одинprivate
Пример:
class Person {
constructor(private name: string){}
}
let person = new Person("Semlinker");
console.log(person.name);
В приведенном выше коде мы создали класс Person, который используетprivate
модификатор определяет частное свойствоname
, затем используйте этот класс для созданияperson
объект, а затем передатьperson.name
посещатьperson
Закрытое свойство объекта, то компилятор TypeScript предложит следующее исключение:
Property 'name' is private and only accessible within class 'Person'.(2341)
Итак, как решить это исключение? Конечно, вы можете использовать утверждение типа для преобразования человека в любой тип:
console.log((person as any).name);
Таким образом, хотя запрос исключения компилятора TypeScript решен, мы все еще можем получить к нему доступ во время выполнения.Person
Частные свойства внутри класса, почему это происходит? Давайте посмотрим на скомпилированный код ES5, возможно, вы знаете ответ:
var Person = /** @class */ (function () {
function Person(name) {
this.name = name;
}
return Person;
}());
var person = new Person("Semlinker");
console.log(person.name);
В настоящее время я считаю, что некоторым небольшим партнерам будет любопытно, и они прошли TypeScript 3.8 и выше.#
Какой код будет сгенерирован после того, как будет скомпилировано приватное поле, определяемое числом:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
Приведенный выше целевой код установлен на ES2015, который скомпилирует и сгенерирует следующий код:
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet)
|| function (receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to set private field on non-instance");
}
privateMap.set(receiver, value);
return value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet)
|| function (receiver, privateMap) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return privateMap.get(receiver);
};
var _name;
class Person {
constructor(name) {
_name.set(this, void 0);
__classPrivateFieldSet(this, _name, name);
}
greet() {
console.log(`Hello, my name is ${__classPrivateFieldGet(this, _name)}!`);
}
}
_name = new WeakMap();
Соблюдая приведенный выше код, используйте#
Частные поля ECMAScript, определенныеWeakMap
объект для хранения, и компилятор сгенерирует__classPrivateFieldSet
и__classPrivateFieldGet
Эти два метода используются для установки значения и получения значения.
С этими проблемами, упомянутыми выше, я полагаю, что некоторые друзья также столкнулись в процессе изучения ТС. Если есть какие-либо неясные места, вы можете оставить мне сообщение или связаться со мной напрямую. После этого Брат Абао продолжит дополнять и улучшать контент в этой области, а заинтересованные друзья могут участвовать вместе.