Подсчитайте проблемы ТС, которые преследовали эти годы

внешний интерфейс TypeScript
Подсчитайте проблемы ТС, которые преследовали эти годы

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проект, он используетPluginDecorator для определения информации о плагине 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Эти два метода используются для установки значения и получения значения.

С этими проблемами, упомянутыми выше, я полагаю, что некоторые друзья также столкнулись в процессе изучения ТС. Если есть какие-либо неясные места, вы можете оставить мне сообщение или связаться со мной напрямую. После этого Брат Абао продолжит дополнять и улучшать контент в этой области, а заинтересованные друзья могут участвовать вместе.

10. Справочные ресурсы