关于前端:细数这些年被困扰过的-TS-问题

15次阅读

共计 14969 个字符,预计需要花费 38 分钟才能阅读完成。

TypeScript 是一种由微软开发的自在和开源的编程语言。它是 JavaScript 的一个超集,而且实质上向这个语言增加了可选的动态类型和基于类的面向对象编程。TypeScript 提供最新的和一直倒退的 JavaScript 个性,包含那些来自 2015 年的 ECMAScript 和将来的提案中的个性,比方异步性能和 Decorators,以帮忙建设强壮的组件。

阿宝哥第一次应用 TypeScript 是在 Angular 2.x 我的项目中,那时候 TypeScript 还没有进入公众的视线。然而当初学习 TypeScript 的小伙伴越来越多了,本文阿宝哥将分享这些年在学习 TypeScript 过程中,曾被困扰过的一些 TS 问题,心愿本文对学习 TypeScript 的小伙伴能有一些帮忙。

好的,上面咱们来开始介绍第一个问题 —— 如何在 window 对象上显式设置属性。

一、如何在 window 对象上显式设置属性

对于应用过 JavaScript 的开发者来说,对于 window.MyNamespace = window.MyNamespace || {}; 这行代码并不会生疏。为了防止开发过程中呈现抵触,咱们个别会为某些性能设置独立的命名空间。

然而,在 TS 中对于 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 会主动进行接口合并,即把单方的成员放到一个同名的接口中。

二、如何为对象动态分配属性

在 JavaScript 中,咱们能够很容易地为对象动态分配属性,比方:

let developer = {};
developer.name = "semlinker";

以上代码在 JavaScript 中能够失常运行,但在 TypeScript 中,编译器会提醒以下异样信息:

Property 'name' does not exist on type '{}'.(2339)

{} 类型示意一个没有蕴含成员的对象,所以该类型没有蕴含 name 属性。为了解决这个问题,咱们能够申明一个 LooseObject 类型:

interface LooseObject {[key: string]: any
}

该类型应用 索引签名 的模式形容 LooseObject 类型能够承受 key 类型是字符串,值的类型是 any 类型的字段。有了 LooseObject 类型之后,咱们就能够通过以下形式来解决上述问题:

interface LooseObject {[key: string]: any
}

let developer: LooseObject = {};
developer.name = "semlinker";

对于 LooseObject 类型来说,它的束缚是很宽松的。在一些利用场景中,咱们除了心愿能反对动静的属性之外,也心愿可能申明一些必选和可选的属性。

比方对于一个示意开发者的 Developer 接口来说,咱们心愿它的 name 属性是必填,而 age 属性是可选的,此外还反对动静地设置字符串类型的属性。针对这个需要咱们能够这样做:

interface Developer {
  name: string;
  age?: number;
  [key: string]: any
}

let developer: Developer = {name: "semlinker"};
developer.age = 30;
developer.city = "XiaMen";

其实除了应用 索引签名 之外,咱们也能够应用 TypeScript 内置的工具类型 Record 来定义 Developer 接口:

// 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 被称为类型变量,它是咱们心愿传递给 identity 函数的类型占位符,同时它被调配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 Number 类型。

其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 能够用任何无效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:

  • K(Key):示意对象中的键类型;
  • V(Value):示意对象中的值类型;
  • E(Element):示意元素类型。

其实并不是只能定义一个类型变量,咱们能够引入心愿定义的任何数量的类型变量。比方咱们引入一个新的类型变量 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 装璜器来定义 IonicNative 中 Device 插件的相干信息:

@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 类。Plugin 工厂函数的定义如下:

// 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;
  };
}

通过观察 Plugin 工厂函数的办法签名,咱们能够晓得调用该函数之后会返回 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 中如何实现依赖注入性能,出于篇幅思考,这里阿宝哥就不持续开展了。感兴趣的小伙伴能够浏览“了不起的 IoC 与 DI”这篇文章。

五、如何了解函数重载的作用

5.1 可恶又可恨的联结类型

因为 JavaScript 是一个动静语言,咱们通常会应用不同类型的参数来调用同一个函数,该函数会依据不同的参数而返回不同的类型的调用后果:

function add(x, y) {return x + y;}

add(1, 2); // 3
add("1", "2"); //"12"

因为 TypeScript 是 JavaScript 的超集,因而以上的代码能够间接在 TypeScript 中应用,但当 TypeScript 编译器开启 noImplicitAny 的配置项时,以上代码会提醒以下错误信息:

Parameter 'x' implicitly has an 'any' type.
Parameter 'y' implicitly has an 'any' type.

该信息通知咱们参数 x 和参数 y 隐式具备 any 类型。为了解决这个问题,咱们能够为参数设置一个类型。因为咱们心愿 add 函数同时反对 string 和 number 类型,因而咱们能够定义一个 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' 这两个字符串作为参数调用 add 函数,并把调用后果保留到一个名为 result 的变量上,这时候咱们想当然的认为此时 result 的变量的类型为 string,所以咱们就能够失常调用字符串对象上的 split 办法。但这时 TypeScript 编译器又呈现以下错误信息了:

Property 'split' does not exist on type 'Combinable'.
Property 'split' does not exist on type 'number'.

很显著 Combinablenumber 类型的对象上并不存在 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;
}

在以上代码中,咱们为 add 函数提供了多个函数类型定义,从而实现函数的重载。在 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 编译器处理函数重载时,它会查找重载列表,尝试应用第一个重载定义。如果匹配的话就应用这个。因而,在定义重载的时候,肯定要把最准确的定义放在最后面。另外在 Calculator 类中,add(a: Combinable, b: Combinable){} 并不是重载列表的一部分,因而对于 add 成员办法来说,咱们只定义了四个重载办法。

六、interfaces 与 type 之间有什么区别

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};

七、object, Object 和 {} 之间有什么区别

7.1 object 类型

object 类型是: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 类的实例的类型,它由以下两个接口来定义:

  • 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.1 数字枚举

在 JavaScript 中布尔类型的变量含有无限范畴的值,即 truefalse。而在 TypeScript 中利用枚举,你也能够自定义类似的类型:

enum NoYes {
  No,
  Yes,
}

NoYes 被称为枚举 NoYes 的成员。每个枚举成员都有一个 name 和一个 value。数字枚举成员值的默认类型是 number 类型。也就是说,每个成员的值都是一个数字:

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 数字枚举 vs 字符串枚举

数字枚举与字符串枚举有什么区别呢?这里咱们来别离看一下数字枚举和字符串枚举编译的后果:

数字枚举编译后果

"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 并不是 Fonum 枚举的成员。为什么会这样呢?咱们来看一下 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

九、应用 # 定义的公有字段与 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)

那如何解决这个异样呢?当然你能够应用类型断言把 person 转为 any 类型:

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 这两个办法用于设置值和获取值。

以上提到的这些问题,置信一些小伙伴们在学习 TS 过程中也遇到了。如果有表述不分明的中央,欢送你们给我留言或间接与我交换。之后,阿宝哥还会持续补充和欠缺这一方面的内容,感兴趣的小伙伴能够一起参加哟。

十、参考资源

  • how-do-you-explicitly-set-a-new-property-on-window-in-typescript
  • how-do-i-dynamically-assign-properties-to-an-object-in-typescript
  • typescript-interfaces-vs-types

十一、举荐浏览

  • 了不起的 TypeScript 入门教程
  • 一文读懂 TypeScript 泛型及利用
  • 你不晓得的 WebSocket
  • 你不晓得的 Blob
  • 你不晓得的 WeakMap

正文完
 0