范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

通俗易懂的TYPESCRIPT入门教程

  本文全面介绍 TypeScript 常见知识点,对于新人入门特别友好!
  本文将带你一步步学习 TypeScript 入门相关的十四个知识点,详细的内容大纲请看下图:
  一、TypeScript 是什么
  TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
  TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。下图显示了 TypeScript 与 ES5、ES2015 和 ES2016 之间的关系:
  1.1 TypeScript 与 JavaScript 的区别
  TypeScriptJavaScriptJavaScript 的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页。可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误强类型,支持静态和动态类型弱类型,没有静态类型选项最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使用支持模块、泛型和接口不支持模块,泛型或接口支持 ES3,ES4,ES5 和 ES6 等不支持编译其他 ES3,ES4,ES5 或 ES6 功能社区的支持仍在增长,而且还不是很大大量的社区支持以及大量文档和解决问题的支持1.2 获取 TypeScript
  命令行的 TypeScript 编译器可以使用 Node.js 包来安装。
  1.安装 TypeScriptnbsp;npm install -g typescript
  2.编译 TypeScript 文件nbsp;tsc helloworld.ts # helloworld.ts => helloworld.js
  当然,对于刚入门 TypeScript 的小伙伴,也可以不用安装 typescript,而是直接使用线上的 TypeScript Playground 来学习新的语法或新特性。
  TypeScript Playground:https://www.typescriptlang.org/play/二、TypeScript 基础类型2.1 Boolean 类型let isDone: boolean = false; // ES5:var isDone = false;2.2 Number 类型let count: number = 10; // ES5:var count = 10;String 类型let name: string = "Semliker"; // ES5:var name = "Semlinker";2.4 Array 类型let list: number[] = [1, 2, 3]; // ES5:var list = [1,2,3];  let list: Array = [1, 2, 3]; // Array泛型语法 // ES5:var list = [1,2,3];2.5 Enum 类型
  使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举。
  1.数字枚举enum Direction {   NORTH,   SOUTH,   EAST,   WEST, }  let dir: Direction = Direction.NORTH;
  默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,Direction.SOUTH 的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。上面的枚举示例代码经过编译后会生成以下代码:"use strict"; var Direction; (function (Direction) {   Direction[(Direction["NORTH"] = 0)] = "NORTH";   Direction[(Direction["SOUTH"] = 1)] = "SOUTH";   Direction[(Direction["EAST"] = 2)] = "EAST";   Direction[(Direction["WEST"] = 3)] = "WEST"; })(Direction || (Direction = {})); var dir = Direction.NORTH;
  当然我们也可以设置 NORTH 的初始值,比如:enum Direction {   NORTH = 3,   SOUTH,   EAST,   WEST, }
  2.字符串枚举
  在 TypeScript 2.4 版本,允许我们使用字符串枚举。在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。enum Direction {   NORTH = "NORTH",   SOUTH = "SOUTH",   EAST = "EAST",   WEST = "WEST", }
  以上代码对于的 ES5 代码如下:"use strict"; var Direction; (function (Direction) {     Direction["NORTH"] = "NORTH";     Direction["SOUTH"] = "SOUTH";     Direction["EAST"] = "EAST";     Direction["WEST"] = "WEST"; })(Direction || (Direction = {}));
  3.异构枚举
  异构枚举的成员值是数字和字符串的混合:enum Enum {   A,   B,   C = "C",   D = "D",   E = 8,   F, }
  以上代码对于的 ES5 代码如下:"use strict"; var Enum; (function (Enum) {     Enum[Enum["A"] = 0] = "A";     Enum[Enum["B"] = 1] = "B";     Enum["C"] = "C";     Enum["D"] = "D";     Enum[Enum["E"] = 8] = "E";     Enum[Enum["F"] = 9] = "F"; })(Enum || (Enum = {}));
  通过观察上述生成的 ES5 代码,我们可以发现数字枚举相对字符串枚举多了 "反向映射":console.log(Enum.A) //输出:0 console.log(Enum[0]) // 输出:A 2.6 Any 类型
  在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。let notSure: any = 666; notSure = "Semlinker"; notSure = false;
  any 类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript 允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查。比如:let value: any;  value.foo.bar; // OK value.trim(); // OK value(); // OK new value(); // OK value[0][1]; // OK
  在许多场景下,这太宽松了。使用 any 类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any 类型,就无法使用 TypeScript 提供的大量的保护机制。为了解决 any 带来的问题,TypeScript 3.0 引入了 unknown 类型。2.7 Unknown 类型
  就像所有类型都可以赋值给 any,所有类型也都可以赋值给 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)。下面我们来看一下 unknown 类型的使用示例:let value: unknown;  value = true; // OK value = 42; // OK value = "Hello World"; // OK value = []; // OK value = {}; // OK value = Math.random; // OK value = null; // OK value = undefined; // OK value = new TypeError(); // OK value = Symbol("type"); // OK
  对 value 变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为 unknown 的值赋值给其他类型的变量时会发生什么?let value: unknown;  let value1: unknown = value; // OK let value2: any = value; // OK let value3: boolean = value; // Error let value4: number = value; // Error let value5: string = value; // Error let value6: object = value; // Error let value7: any[] = value; // Error let value8: Function = value; // Error
  unknown 类型只能被赋值给 any 类型和 unknown 类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。
  现在让我们看看当我们尝试对类型为 unknown 的值执行操作时会发生什么。以下是我们在之前 any 章节看过的相同操作:let value: unknown;  value.foo.bar; // Error value.trim(); // Error value(); // Error new value(); // Error value[0][1]; // Error
  将 value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。2.8 Tuple 类型
  众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其工作方式类似于数组。
  元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。为了更直观地理解元组的概念,我们来看一个具体的例子:let tupleType: [string, boolean]; tupleType = ["Semlinker", true];
  在上面代码中,我们定义了一个名为 tupleType 的变量,它的类型是一个类型数组 [string, boolean],然后我们按照正确的类型依次初始化 tupleType 变量。与数组一样,我们可以通过下标来访问元组中的元素:console.log(tupleType[0]); // Semlinker console.log(tupleType[1]); // true
  在元组初始化的时候,如果出现类型不匹配的话,比如:tupleType = [true, "Semlinker"];
  此时,TypeScript 编译器会提示以下错误信息:[0]: Type "true" is not assignable to type "string". [1]: Type "string" is not assignable to type "boolean".
  很明显是因为类型不匹配导致的。在元组初始化的时候,我们还必须提供每个属性的值,不然也会出现错误,比如:tupleType = ["Semlinker"];
  此时,TypeScript 编译器会提示以下错误信息:Property "1" is missing in type "[string]" but required in type "[string, boolean]". 2.9 Void 类型
  某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:// 声明函数返回值为void function warnUser(): void {   console.log("This is my warning message"); }
  以上代码编译生成的 ES5 代码如下:"use strict"; function warnUser() {   console.log("This is my warning message"); }
  需要注意的是,声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefined 或 null:let unusable: void = undefined; 2.10 Null 和 Undefined 类型
  TypeScript 里,undefined 和 null 两者有各自的类型分别为 undefined 和 null。let u: undefined = undefined; let n: null = null;
  默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。然而,如果你指定了--strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自的类型。2.11 Never 类型
  never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。// 返回never的函数必须存在无法达到的终点 function error(message: string): never {   throw new Error(message); }  function infiniteLoop(): never {   while (true) {} }
  在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:type Foo = string | number;  function controlFlowAnalysisWithNever(foo: Foo) {   if (typeof foo === "string") {     // 这里 foo 被收窄为 string 类型   } else if (typeof foo === "number") {     // 这里 foo 被收窄为 number 类型   } else {     // foo 在这里是 never     const check: never = foo;   } }
  注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事修改了 Foo 的类型:type Foo = string | number | boolean;
  然而他忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保
  controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。 通过这个示例,我们可以得出一个结论:使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。三、TypeScript 断言
  有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
  通过类型断言这种方式可以告诉编译器,"相信我,我知道自己在干什么"。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
  类型断言有两种形式:3.1 "尖括号" 语法let someValue: any = "this is a string"; let strLength: number = (someValue).length; 3.2 as 语法let someValue: any = "this is a string"; let strLength: number = (someValue as string).length; 四、类型守卫
  A type guard is some expression that performs a runtime check that guarantees the type in some scope. —— TypeScript 官方文档
  类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:4.1 in 关键字interface Admin {   name: string;   privileges: string[]; }  interface Employee {   name: string;   startDate: Date; }  type UnknownEmployee = Employee | Admin;  function printEmployeeInformation(emp: UnknownEmployee) {   console.log("Name: " + emp.name);   if ("privileges" in emp) {     console.log("Privileges: " + emp.privileges);   }   if ("startDate" in emp) {     console.log("Start Date: " + emp.startDate);   } } 4.2 typeof 关键字function padLeft(value: string, padding: string | number) {   if (typeof padding === "number") {       return Array(padding + 1).join(" ") + value;   }   if (typeof padding === "string") {       return padding + value;   }   throw new Error(`Expected string or number, got "${padding}".`); }
  typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必须是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。4.3 instanceof 关键字interface Padder {   getPaddingString(): string; }  class SpaceRepeatingPadder implements Padder {   constructor(private numSpaces: number) {}   getPaddingString() {     return Array(this.numSpaces + 1).join(" ");   } }  class StringPadder implements Padder {   constructor(private value: string) {}   getPaddingString() {     return this.value;   } }  let padder: Padder = new SpaceRepeatingPadder(6);  if (padder instanceof SpaceRepeatingPadder) {   // padder的类型收窄为 "SpaceRepeatingPadder" } 4.4 自定义类型保护的类型谓词function isNumber(x: any): x is number {   return typeof x === "number"; }  function isString(x: any): x is string {   return typeof x === "string"; } 五、联合类型和类型别名5.1 联合类型
  联合类型通常与 null 或 undefined 一起使用:const sayHello = (name: string | undefined) => {   /* ... */ };
  例如,这里 name 的类型是 string | undefined 意味着可以将 string 或 undefined 的值传递给sayHello 函数。sayHello("Semlinker"); sayHello(undefined);
  通过这个示例,你可以凭直觉知道类型 A 和类型 B 联合后的类型是同时接受 A 和 B 值的类型。5.2 可辨识联合
  TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。
  这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
  1.可辨识
  可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:enum CarTransmission {   Automatic = 200,   Manual = 300 }  interface Motorcycle {   vType: "motorcycle"; // discriminant   make: number; // year }  interface Car {   vType: "car"; // discriminant   transmission: CarTransmission }  interface Truck {   vType: "truck"; // discriminant   capacity: number; // in tons }
  在上述代码中,我们分别定义了 Motorcycle、 Car 和 Truck 三个接口,在这些接口中都包含一个 vType 属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
  2.联合类型
  基于前面定义了三个接口,我们可以创建一个 Vehicle 联合类型:type Vehicle = Motorcycle | Car | Truck;
  现在我们就可以开始使用 Vehicle 联合类型,对于 Vehicle 类型的变量,它可以表示不同类型的车辆。
  3.类型守卫
  下面我们来定义一个 evaluatePrice 方法,该方法用于根据车辆的类型、容量和评估因子来计算价格,具体实现如下:const EVALUATION_FACTOR = Math.PI;  function evaluatePrice(vehicle: Vehicle) {   return vehicle.capacity * EVALUATION_FACTOR; }  const myTruck: Truck = { vType: "truck", capacity: 9.5 }; evaluatePrice(myTruck);
  对于以上代码,TypeScript 编译器将会提示以下错误信息:Property "capacity" does not exist on type "Vehicle". Property "capacity" does not exist on type "Motorcycle".
  原因是在 Motorcycle 接口中,并不存在 capacity 属性,而对于 Car 接口来说,它也不存在 capacity 属性。那么,现在我们应该如何解决以上问题呢?这时,我们可以使用类型守卫。下面我们来重构一下前面定义的 evaluatePrice 方法,重构后的代码如下:function evaluatePrice(vehicle: Vehicle) {   switch(vehicle.vType) {     case "car":       return vehicle.transmission * EVALUATION_FACTOR;     case "truck":       return vehicle.capacity * EVALUATION_FACTOR;     case "motorcycle":       return vehicle.make * EVALUATION_FACTOR;   } }
  在以上代码中,我们使用 switch 和 case 运算符来实现类型守卫,从而确保在 evaluatePrice 方法中,我们可以安全地访问 vehicle 对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。5.3 类型别名
  类型别名用来给一个类型起个新名字。type Message = string | string[];  let greet = (message: Message) => {   // ... }; 六、交叉类型
  TypeScript 交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。interface IPerson {   id: string;   age: number; }  interface IWorker {   companyId: string; }  type IStaff = IPerson & IWorker;  const staff: IStaff = {   id: "E1006",   age: 33,   companyId: "EFT" };  console.dir(staff)
  在上面示例中,我们首先为 IPerson 和 IWorker 类型定义了不同的成员,然后通过 & 运算符定义了 IStaff 交叉类型,所以该类型同时拥有 IPerson 和 IWorker 这两种类型的成员。七、TypeScript 函数7.1 TypeScript 函数与 JavaScript 函数的区别
  TypeScriptJavaScript含有类型无类型箭头函数箭头函数(ES2015)函数类型无函数类型必填和可选参数所有参数都是可选的默认参数默认参数剩余参数剩余参数函数重载无函数重载7.2 箭头函数
  1.常见语法myBooks.forEach(() => console.log("reading"));  myBooks.forEach(title => console.log(title));  myBooks.forEach((title, idx, arr) =>   console.log(idx + "-" + title); );  myBooks.forEach((title, idx, arr) => {   console.log(idx + "-" + title); });
  2.使用示例// 未使用箭头函数 function Book() {   let self = this;   self.publishDate = 2016;   setInterval(function () {     console.log(self.publishDate);   }, 1000); }  // 使用箭头函数 function Book() {   this.publishDate = 2016;   setInterval(() => {     console.log(this.publishDate);   }, 1000); } 7.3 参数类型和返回类型function createUserId(name: string, id: number): string {   return name + id; } 7.4 函数类型let IdGenerator: (chars: string, nums: number) => string;  function createUserId(name: string, id: number): string {   return name + id; }  IdGenerator = createUserId; 7.5 可选参数及默认参数// 可选参数 function createUserId(name: string, id: number, age?: number): string {   return name + id; }  // 默认参数 function createUserId(  name: string = "Semlinker",   id: number,   age?: number): string {   return name + id; }
  在声明函数时,可以通过 ? 号来定义可选参数,比如 age?: number 这种形式。在实际使用时,需要注意的是可选参数要放在普通参数的后面,不然会导致编译错误。7.6 剩余参数function push(array, ...items) {   items.forEach(function (item) {     array.push(item);   }); }  let a = []; push(a, 1, 2, 3); 7.7 函数重载
  函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。要解决前面遇到的问题,方法就是为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用。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) {   if (typeof a === "string" || typeof b === "string") {     return a.toString() + b.toString();   }   return a + b; }
  在以上代码中,我们为 add 函数提供了多个函数类型定义,从而实现函数的重载。之后,可恶的错误消息又消失了,因为这时 result 变量的类型是 string 类型。在 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 成员方法来说,我们只定义了四个重载方法。八、TypeScript 数组8.1 数组结构let x: number; let y: number; let z: number; let five_array = [0,1,2,3,4]; [x,y,z] = five_array; 8.2 数组展开运算符let two_array = [0, 1]; let five_array = [...two_array, 2, 3, 4]; 8.3 数组遍历let colors: string[] = ["red", "green", "blue"]; for (let i of colors) {   console.log(i); } 九、TypeScript 对象9.1 对象解构let person = {   name: "Semlinker",   gender: "Male", };  let { name, gender } = person; 9.2 对象展开运算符let person = {   name: "Semlinker",   gender: "Male",   address: "Xiamen", };  // 组装对象 let personWithAge = { ...person, age: 33 };  // 获取除了某些项外的其它项 let { name, ...rest } = person; 十、TypeScript 接口
  在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。
  TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。10.1 对象的形状interface Person {   name: string;   age: number; }  let Semlinker: Person = {   name: "Semlinker",   age: 33, }; 10.2 可选 | 只读属性interface Person {   readonly name: string;   age?: number; }
  只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray 类型,它与 Array相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray = a; ro[0] = 12; // error! ro.push(5); // error! ro.length = 100; // error! a = ro; // error! 十一、TypeScript 类11.1 类的属性与方法
  在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。
  在 TypeScript 中,我们可以通过 Class 关键字来定义一个类:class Greeter {   // 静态属性   static cname: string = "Greeter";   // 成员属性   greeting: string;    // 构造函数 - 执行初始化操作   constructor(message: string) {     this.greeting = message;   }    // 静态方法   static getClassName() {     return "Class name is Greeter";   }    // 成员方法   greet() {     return "Hello, " + this.greeting;   } }  let greeter = new Greeter("world");
  那么成员属性与静态属性,成员方法与静态方法有什么区别呢?这里无需过多解释,我们直接看一下以下编译生成的 ES5 代码:"use strict"; var Greeter = /** @class */ (function () {     // 构造函数 - 执行初始化操作     function Greeter(message) {         this.greeting = message;     }     // 静态方法     Greeter.getClassName = function () {         return "Class name is Greeter";     };     // 成员方法     Greeter.prototype.greet = function () {         return "Hello, " + this.greeting;     };     // 静态属性     Greeter.cname = "Greeter";     return Greeter; }()); var greeter = new Greeter("world"); 11.2 访问器
  在 TypeScript 中,我们可以通过 getter 和 setter 方法来实现数据的封装和有效性校验,防止出现异常数据。let passcode = "Hello TypeScript";  class Employee {   private _fullName: string;    get fullName(): string {     return this._fullName;   }    set fullName(newName: string) {     if (passcode && passcode == "Hello TypeScript") {       this._fullName = newName;     } else {       console.log("Error: Unauthorized update of employee!");     }   } }  let employee = new Employee(); employee.fullName = "Semlinker"; if (employee.fullName) {   console.log(employee.fullName); } 11.3 类的继承
  继承 (Inheritance) 是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。
  继承是一种 is-a 关系:
  在 TypeScript 中,我们可以通过 extends 关键字来实现继承:class Animal {   name: string;      constructor(theName: string) {     this.name = theName;   }      move(distanceInMeters: number = 0) {     console.log(`${this.name} moved ${distanceInMeters}m.`);   } }  class Snake extends Animal {   constructor(name: string) {     super(name);   }      move(distanceInMeters = 5) {     console.log("Slithering...");     super.move(distanceInMeters);   } }  let sam = new Snake("Sammy the Python"); sam.move(); 11.4 ECMAScript 私有字段
  在 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);私有字段不能在包含的类之外访问,甚至不能被检测到。十二、TypeScript 泛型
  软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
  在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
  设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
  泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。12.1 泛型接口interface GenericIdentityFn {   (arg: T): T; } 12.2 泛型类class GenericNumber {   zeroValue: T;   add: (x: T, y: T) => T; }  let myGenericNumber = new GenericNumber(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function (x, y) {   return x + y; }; 12.3 泛型变量
  对刚接触 TypeScript 泛型的小伙伴来说,看到 T 和 E,还有 K 和 V 这些泛型变量时,估计会一脸懵逼。其实这些大写字母并没有什么本质的区别,只不过是一个约定好的规范而已。也就是说使用大写字母 A-Z 定义的类型变量都属于泛型,把 T 换成 A,也是一样的。下面我们介绍一下一些常见泛型变量代表的意思:T(Type):表示一个 TypeScript 类型K(Key):表示对象中的键类型V(Value):表示对象中的值类型E(Element):表示元素类型12.4 泛型工具类型
  为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。出于篇幅考虑,这里我们只简单介绍 Partial 工具类型。不过在具体介绍之前,我们得先介绍一些相关的基础知识,方便读者自行学习其它的工具类型。
  1.typeof
  在 TypeScript 中,typeof 操作符可以用来获取一个变量声明或对象的类型。interface Person {   name: string;   age: number; }  const sem: Person = { name: "semlinker", age: 30 }; type Sem= typeof sem; // -> Person  function toArray(x: number): Array {   return [x]; }  type Func = typeof toArray; // -> (x: number) => number[]
  2.keyof
  keyof 操作符可以用来一个对象中的所有 key 值:interface Person {     name: string;     age: number; }  type K1 = keyof Person; // "name" | "age" type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"  type K3 = keyof { [x: string]: Person };  // string | number
  3.in
  in 用来遍历枚举类型:type Keys = "a" | "b" | "c"  type Obj =  {   [p in Keys]: any } // -> { a: any, b: any, c: any }
  4.infer
  在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。type ReturnType = T extends (   ...args: any[] ) => infer R ? R : any;
  以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
  5.extends
  有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。interface ILengthwise {   length: number; }  function loggingIdentity(arg: T): T {   console.log(arg.length);   return arg; }
  现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:loggingIdentity(3);  // Error, number doesn"t have a .length property
  这时我们需要传入符合约束类型的值,必须包含必须的属性:loggingIdentity({length: 10, value: 3});
  6.Partial
  Partial 的作用就是将某个类型里的属性全部变为可选项 ?。
  定义:/**  * node_modules/typescript/lib/lib.es5.d.ts  * Make all properties in T optional  */ type Partial = {   [P in keyof T]?: T[P]; };
  在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。
  示例:interface Todo {   title: string;   description: string; }  function updateTodo(todo: Todo, fieldsToUpdate: Partial) {   return { ...todo, ...fieldsToUpdate }; }  const todo1 = {   title: "organize desk",   description: "clear clutter", };  const todo2 = updateTodo(todo1, {   description: "throw out trash", });
  在上面的 updateTodo 方法中,我们利用 Partial 工具类型,定义 fieldsToUpdate 的类型为 Partial,即:{    title?: string | undefined;    description?: string | undefined; } 十三、TypeScript 装饰器13.1 装饰器是什么它是一个表达式该表达式被执行后,返回一个函数函数的入参分别为 target、name 和 descriptor执行该函数后,可能返回 descriptor 对象,用于配置 target 对象13.2 装饰器的分类类装饰器(Class decorators)属性装饰器(Property decorators)方法装饰器(Method decorators)参数装饰器(Parameter decorators)13.3 类装饰器
  类装饰器声明:declare type ClassDecorator = (   target: TFunction ) => TFunction | void;
  类装饰器顾名思义,就是用来装饰类的。它接收一个参数:target: TFunction – 被装饰的类
  看完第一眼后,是不是感觉都不好了。没事,我们马上来个例子:function Greeter(target: Function): void {   target.prototype.greet = function (): void {     console.log("Hello Semlinker!");   }; }  @Greeter class Greeting {   constructor() {     // 内部实现   } }  let myGreeting = new Greeting(); myGreeting.greet(); // console output: "Hello Semlinker!";
  上面的例子中,我们定义了 Greeter 类装饰器,同时我们使用了 @Greeter 语法糖,来使用装饰器。
  友情提示:读者可以直接复制上面的代码,在 TypeScript Playground 中运行查看结果。
  有的读者可能想问,例子中总是输出 Hello Semlinker! ,能自定义输出的问候语么 ?这个问题很好,答案是可以的。
  具体实现如下:function Greeter(greeting: string) {   return function (target: Function) {     target.prototype.greet = function (): void {       console.log(greeting);     };   }; }  @Greeter("Hello TS!") class Greeting {   constructor() {     // 内部实现   } }  let myGreeting = new Greeting(); myGreeting.greet(); // console output: "Hello TS!"; 13.4 属性装饰器
  属性装饰器声明:declare type PropertyDecorator = (target:Object,    propertyKey: string | symbol ) => void;
  属性装饰器顾名思义,用来装饰类的属性。它接收两个参数:target: Object – 被装饰的类propertyKey: string | symbol – 被装饰类的属性名
  趁热打铁,马上来个例子热热身:function logProperty(target: any, key: string) {   delete target[key];    const backingField = "_" + key;    Object.defineProperty(target, backingField, {     writable: true,     enumerable: true,     configurable: true   });    // property getter   const getter = function (this: any) {     const currVal = this[backingField];     console.log(`Get: ${key} => ${currVal}`);     return currVal;   };    // property setter   const setter = function (this: any, newVal: any) {     console.log(`Set: ${key} => ${newVal}`);     this[backingField] = newVal;   };    // Create new property with getter and setter   Object.defineProperty(target, key, {     get: getter,     set: setter,     enumerable: true,     configurable: true   }); }  class Person {    @logProperty   public name: string;    constructor(name : string) {      this.name = name;   } }  const p1 = new Person("semlinker"); p1.name = "kakuqo";
  以上代码我们定义了一个 logProperty 函数,来跟踪用户对属性的操作,当代码成功运行后,在控制台会输出以下结果:Set: name => semlinker Set: name => kakuqo 13.5 方法装饰器
  方法装饰器声明:declare type MethodDecorator = (target:Object, propertyKey: string | symbol,       descriptor: TypePropertyDescript) => TypedPropertyDescriptor | void;
  方法装饰器顾名思义,用来装饰类的方法。它接收三个参数:target: Object – 被装饰的类propertyKey: string | symbol – 方法名descriptor: TypePropertyDescript – 属性描述符
  废话不多说,直接上例子:function LogOutput(tarage: Function, key: string, descriptor: any) {   let originalMethod = descriptor.value;   let newMethod = function(...args: any[]): any {     let result: any = originalMethod.apply(this, args);     if(!this.loggedOutput) {       this.loggedOutput = new Array();     }     this.loggedOutput.push({       method: key,       parameters: args,       output: result,       timestamp: new Date()     });     return result;   };   descriptor.value = newMethod; }  class Calculator {   @LogOutput   double (num: number): number {     return num * 2;   } }  let calc = new Calculator(); calc.double(11); // console ouput: [{method: "double", output: 22, ...}] console.log(calc.loggedOutput); 
  下面我们来介绍一下参数装饰器。13.6 参数装饰器
  参数装饰器声明:declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,    parameterIndex: number ) => void
  参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:target: Object – 被装饰的类propertyKey: string | symbol – 方法名parameterIndex: number – 方法中参数的索引值function Log(target: Function, key: string, parameterIndex: number) {   let functionLogged = key || target.prototype.constructor.name;   console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has  been decorated`); }  class Greeter {   greeting: string;   constructor(@Log phrase: string) {  this.greeting = phrase;    } }  // console output: The parameter in position 0  // at Greeter has been decorated
  介绍完 TypeScript 入门相关的基础知识,猜测很多刚入门的小伙伴已有 "从入门到放弃" 的想法,最后我们来简单介绍一下编译上下文。十四、编译上下文14.1 tsconfig.json 的作用用于标识 TypeScript 项目的根路径;用于配置 TypeScript 编译器;用于指定编译的文件。14.2 tsconfig.json 重要字段files – 设置要编译的文件的名称;include – 设置需要进行编译的文件,支持路径模式匹配;exclude – 设置无需进行编译的文件,支持路径模式匹配;compilerOptions – 设置与编译流程相关的选项。14.3 compilerOptions 选项
  compilerOptions 支持很多选项,常见的有 baseUrl、 target、baseUrl、 moduleResolution 和 lib 等。
  compilerOptions 每个选项的详细说明如下:{   "compilerOptions": {      /* 基本选项 */     "target": "es5",                       // 指定 ECMAScript 目标版本: "ES3" (default), "ES5", "ES6"/"ES2015", "ES2016", "ES2017", or "ESNEXT"     "module": "commonjs",                  // 指定使用模块: "commonjs", "amd", "system", "umd" or "es2015"     "lib": [],                             // 指定要包含在编译中的库文件     "allowJs": true,                       // 允许编译 javascript 文件     "checkJs": true,                       // 报告 javascript 文件中的错误     "jsx": "preserve",                     // 指定 jsx 代码的生成: "preserve", "react-native", or "react"     "declaration": true,                   // 生成相应的 ".d.ts" 文件     "sourceMap": true,                     // 生成相应的 ".map" 文件     "outFile": "./",                       // 将输出文件合并为一个文件     "outDir": "./",                        // 指定输出目录     "rootDir": "./",                       // 用来控制输出目录结构 --outDir.     "removeComments": true,                // 删除编译后的所有的注释     "noEmit": true,                        // 不生成输出文件     "importHelpers": true,                 // 从 tslib 导入辅助工具函数     "isolatedModules": true,               // 将每个文件做为单独的模块 (与 "ts.transpileModule" 类似).      /* 严格的类型检查选项 */     "strict": true,                        // 启用所有严格类型检查选项     "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错     "strictNullChecks": true,              // 启用严格的 null 检查     "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误     "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 "use strict"      /* 额外的检查 */     "noUnusedLocals": true,                // 有未使用的变量时,抛出错误     "noUnusedParameters": true,            // 有未使用的参数时,抛出错误     "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误     "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)      /* 模块解析选项 */     "moduleResolution": "node",            // 选择模块解析策略: "node" (Node.js) or "classic" (TypeScript pre-1.6)     "baseUrl": "./",                       // 用于解析非相对模块名称的基目录     "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表     "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容     "typeRoots": [],                       // 包含类型声明的文件列表     "types": [],                           // 需要包含的类型声明文件名列表     "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。      /* Source Map Options */     "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置     "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置     "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件     "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性      /* 其他选项 */     "experimentalDecorators": true,        // 启用装饰器     "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持   } }
  看到这里的读者都是"真爱",如果你还意犹未尽,那就来看看本人整理的 Github 上 1.5K+ 的开源项目:awesome-typescript。
  https://github.com/semlinker/awesome-typescript十五、参考资源mariusschulz – the-unknown-type-in-typescript深入理解 TypeScript – 编译上下文

从乌托邦到伊甸园万魔降噪真无线林氏评测就拿我喜欢的足球来说吧,近期因为众所周知的缘故,不光欧洲足球五大联赛纷纷停摆,连欧洲杯都被延后了一年,对于诸多宅在家里的球迷来说,这实在不是个好消息。足球,作为世界上最流行的竞技运满分的小恶魔飞朵天狼星MKII6789林氏评测好了,这声音不用调了直接上市我会没有任何意见。说这话的时候,我和飞朵的林总正在一起参加朋友婚礼,他抽空把定版前的天狼星MKII拿出来,给我听了一耳朵。也没带什么器材,就随便怼手机上圈铁真无线的理想主义声武士HD3TWS林氏评测很久之前,我看过一个叫做玛法达的漫画,由阿根廷漫画家季诺于半个世纪以前创作,印象最深的是小女孩玛法达时常吐槽周围的一切都是MadeinUSA,成天吐槽,看不惯又无可奈何的样子现在想意大利宾尼法利纳设计打造,CCPC紧凑型SUV六冠王,谁还考虑博越9月26日,东南DX5全国正式上市,售价6。99万元9。99万元,全系满足国六B排放标准。作为东南汽车DX家族的新晋成员,东南DX5完美承袭了家族优秀基因,从大气颜值精致品质舒适驾8月MPV市场销量分析新格局应运而生?乘联会数据显示,8月全国乘用车市场零售156。4万台,同比下降9。9,环比增长5。4。今年18月狭义乘用车零售下降8。9,8月零售同比增速稍低于年累计增速8月的零售环比增长5。4是中国最快完成80万销量紧凑型SUV,仅用时3年,比哈弗H6更火9月29日,吉利博越家族56000公里品质马拉松环中国行车队自腾冲启动仪式后,经过10天的跋山涉水,穿越云贵高原,完成新长征第一阶段挑战,抵达山城重庆。在这座神奇的3D魔幻都市,博iQOO5系列发布15分钟可充至1002020年8月17日,智能手机品牌iQOO召开新品发布会,正式推出旗下5G性能旗舰iQOO5系列。iQOO5系列采用120W超快闪充技术,引领行业闪充革命,其超强配置带来专业级的电从颜值到内涵都是赢家TANCHJIMHANA林氏评测在见到HANA之前,我以为我算是比较了解贪吃鸡(TANCHJIM)这个品牌了,至少心里有谱。这是一家1国内原创设计师主导每款产品都一定比市面上竞品更漂亮2对于技术方面有自己章程,不如此易用又HiFi的飞利浦X3,不值得心动么?我开门见山的跟大家说,飞利浦这个FidelioX3(以下简称飞利浦X3),不是有些人以为的那种HiFi耳机。我是说,它不是那种,我为了音质我啥都不管了,我做得很难推,很挑前端,然后我很难想象达音科SA6只卖3899元十多年前耳机玩家里有个说法,叫做3210,指代的是威士顿ES3Sensaphonic的2XS以及UE10,这是当年的三个万元级多单元动铁标杆,是那个时候耳塞里真正意义上的天花板,别我觉得你误会三星BudsLive了,你要不要听我解释?三星这几年在耳机方面,其走位完全不是风骚二字就能形容的当你以为Buds卖挺好,Buds的下一代会是小改款时,人家直接把单元换了,乍一看外观没啥变化,Buds的音质和通话质量续航却大
手机公司纷纷入局芯片行业,为何总是失败?各种公司纷纷布局芯片,已经是趋势了。特别是财大气粗的手机公司,可以说是要钱有钱,要人有人,按道理成功不难,然而,入局者众多,但是鲜有成功的。什么原因?还是在于难度。芯片设计的难度相开发APP要注意的6大要素随着移动设备与智能手机的普及,各种手机应用程式(APP)应运而生。很多企业顺应趋势纷纷为业务开发APP,而有些资料是您开发APP前需要留意的。希望您能花点时间阅读这篇文章,找开发A神推荐西瓜导航你值得拥有西瓜导航(xgnav。com)是国内专业的网址导航和网站导航网站,汇集全网优质网址和资源,为你提供站长资源二次元服务器wordpress设计师区块链产品经理程序员等资源西瓜导航,网202122新闻和报纸摘要简讯国内简讯国家十三五科技创新成就展于10月21日在北京展览馆开幕,本次展览以创新驱动发展迈向科技强国为主题。从新一代人造太阳装置,到时速600公里高速磁浮列车,集中展示十三五期间我国OPPO反诉诺基亚5G专利侵权案已被德国法院立案财经网科技10月22日讯,据集微网消息,近日,OPPO反诉诺基亚的4件5G专利侵权案已被德国曼海姆慕尼黑汉堡法院立案。有趣的是,上述诉讼地中,曼海姆慕尼黑同样是此前诺基亚起诉OPP国泰君安元宇宙爆发期或临近,建议关注VRAR产业以及虚拟文娱产业投资机会智通财经APP获悉,国泰君安发布研究报告称,鉴于VR设备在Facebook字节跳动的推动下普及在望,以区块链技术为基的NFT游戏呈现出经济体系雏形,Roblox上市后用数据展示其活为什么安卓手机Typec数据线c头触点有的满脚,有的不满脚?TypeC只是一种接口的形状,它的接口的功能都是可以被增减的。这得看厂商的意愿,TypeC可以做成只支持USB数据传输,不能充电,也不能用于显示可以做成只支持DP显示和数据传输,不曾经风靡中国的五笔,彻底败给拼音输入法,它为什么被用户抛弃?专门用五笔打出这段话来回复你一下。五笔并没有被使用它的人抛弃,只是用五笔的人在老去。大众用拼音,专业用五笔五笔没有打不出来的字,拼音没有打出而不认识的字简单点,还是拼音输入法门槛低用户需求巨大,利用率却偏低互联网医院为何尴尬?来源中国经济网经济日报原标题互联网医院建好更要用好近年来,在各项配套政策支持下,我国互联网医院发展迅速。截至目前,全国已有互联网医院1600多家,仅今年上半年就新增500家,远程会每日优鲜成为用户直通产地的优质桥梁市场需求是行业发展的主要原因,自然,生鲜行业的发展自然也离不开用户日益提高的需求。物联网AI科技智能移动端的快速发展也为生鲜行业的发展提供了助力。线上商铺猛增,竞争自然也变的激烈,基于C开发ModbusTCP服务器实现数据接口Begin前言有一个项目使用的是西门子的PLC,你开发了一个上位机,现在有一个第三方软件也想要获取西门子PLC的数据,希望你能提供一个接口,该如何实现。我相信你们应该会遇到这种问题