来自技术胖博客,我只是一个搬运工

1 Typescript环境搭建

如果你想使用 TypeScript 来编写代码,你需要先安装一下它的开发环境,这并不复杂。

1.安装 Node 的运行环境

你可以到Node.js官网去下载 Node 进行安装( https://node.js.org ),建议你下载LTS版本,也就是长期支持版本。安装的过程我就不演示了,这个过程就和安装 QQ 一样,没有任何难度。

如果你已经安装了,可以打开命令行工具,然后使用node -v命令查看安装的版本,但是一般还有一个命令需要检测一下,就是npm -v,如果两个命令都可以输出版本号,说明你的 Node 安装已经没有任何问题了。

2.全局安装 typeScript

你要使用 TypeScript 先要在你的系统中全局安装一下TypeScript,这里你可以直接在 VSCode 中进行安装,安装命令可以使用 npm 也可以使用 yarn

npm install typescript -g
yarn global add typescript

这两个你使用那个都是可以的,根据喜好自行选择,我使用的npm进行安装。

我们在一个目录下新建Demo1.ts,输入下面的代码:

function hello() {
  let web: string = "Hello World";
  console.log(web);
}

hello();

使用node Demo1.ts时,会出现报错。所以我们需要先tsc Demo.ts,在目录下将会生成Demo1.js,我们再node Demo1.js就没事了

1.1 ts-node 工具

但是这样操作的效率实在是太低了,你可以使用ts-node插件来解决这个问题,有了这个插件,我们就不用再编译了,而使用ts-node就可以直接看到编写结果。

使用npm命令来全局安装,直接在命令行输入下面的命令:

npm install -g ts-node

安装完成后,就可以在命令中直接输入如下命令,来查看结果了。

ts-node Demo1.ts

Demo1.ts

function hello() {
  let web: string = "Hello World";
  console.log(web);
}

hello();

2 Typescript静态类型

2.1 一般静态类型

下面的代码是一般的,也是最简单的变量静态类型

 // 1 一般的静态类型
 const count: number = 1; //此时number这个变量值为0

这里的: number就是定义了一个静态类型。

这样定义后count这个变量在程序中就永远都是数字类型了,不可以改变了。

比如我们这时候给count复制一个字符串,它就报错了。

// 错误示范
const count: number = 1
count = "Wibus"; //这里的代码VSC将会报错提示:Cannot assign to 'count' because it is a constant.(无法分配到 "count" ,因为它是常数。)

你也可以发现这时候的count变量,可以使用number类型上所有的属性和方法。

当然我们还可以使用其他的类型,如string(字符串)

2.2 自定义静态类型

下面的代码展示了什么是 自定义静态类型

//2 自定义静态类型
interface Student {
  name: string; //string - 字符串;
  age: number; //number 常数
}
const Ming: Student = {
  name: "小明", //可以填写字符串
  age: 14, //只能填写数字
};

interface 是我们之后需要学到的接口,这部分暂时不说

这时候你如果声明变量,跟自定义不一样,VSCode直接就会报错。需要注意的是,这时候Ming变量也具有name和age属性了。

你需要记住的是,如果使用了静态类型,不仅意味着变量的类型不可以改变,还意味着类型的属性和方法也跟着确定了。这个特点就大大提高了程序的健壮性,并且编辑器这时候也会给你很好的语法提示,加快了你的开发效率。

Demo2.ts

/**
 * Demo2.ts
 * 介绍typescript的静态类型
 * @date 2021-1-1
 * @author Wibus
 */

 // 1 一般的静态类型
 const count: number = 1; //此时number这个变量值为0

/**
  * 这里的: number就是定义了一个静态类型。
  * 这样定义后count这个变量在程序中就永远都是数字类型了,不可以改变了。
  * 比如我们这时候给count复制一个字符串,它就报错了。
  * count = "Wibus";
  */

count = "Wibus"; //这里的代码VSC将会报错提示:Cannot assign to 'count' because it is a constant.(无法分配到 "count" ,因为它是常数。)

//2 自定义静态类型
interface Student {
  name: string; //String - 字符串;
  age: number; //number 常数
}
const Ming: Student = {
  name: "小明", //可以填写字符串
  age: 14, //只能填写数字
};
//这时候你如果声明变量,跟自定义不一样,VSCode直接就会报错。需要注意的是,这时候Ming变量就变得具有name和age属性了

/**
 * 如果使用了静态类型,不仅意味着变量的类型不可以改变,还意味着类型的属性和方法也跟着确定了。
 * 这个特点就大大提高了程序的健壮性,并且编辑器这时候也会给你很好的语法提示,加快了你的开发效率。
 */

3 Typescript 基础静态类型和对象类型

在 TypeScript 静态类型分为两种,一种是基础静态类型,一种是对象类型

3.1 基础静态类型

基础静态类型非常的简单,只需要在变量名后加上 : 之后加上类型名即可,例子如下:

const counter : number = 918; // 类型 number only数字
const myName : string = "Wibus"; // 类型 string only字符串

类型不止这些,还有null,undefinde,symbol,booleanvoidnerver这些都是最常用的基础数据类型

3.2 对象类型

先来看一个例子,通过例子有经验的小伙伴就知道个大概了,然后我们再来讲解(其实上节课我们也讲到了,我们这里就当复习了)。

新建一个文件demo3.ts(你可以跟我不一样),然后写下如下代码。

3.2.1 最简单的对象类型

const xiaoGeGe: {
    name: string, //字符串类型
    age: number, //常数类型
  } = {
    name: "Wibus", //对应的数据
    age: 14,
  };
  console.log(xiaoGeGe.name);

写完后,我们在 terminal(终端) 中输入ts-node demo3.ts,可以看到结果输出了Wibus。这就是一个经典的对象类型,也是最简单的对象类型。

3.2.2 数组对象类型

对象类型也可以是数组,比如现在我们需要很多小姐姐,我们就可以这样写。

const xiaoJieJies : string[] = ["Awa", "Qwq", "老婆"];

这时候的意思是,变量xiaoJieJies必须是一个数组,数组里的内容必须是字符串。你可以试着把字符串改为数字,VSCode会直接给我们报错。

// ⚠️错误示范
const ErrorxiaoJieJies : string[] = [123, "Qwq", "老婆"]; //VSC将会直接报错

3.2.3 类类型

来看看下面的代码。这个代码就是用类的形式,来定义变量。

class Person {} //定义一个Person类
const Js: Person = new Person();

这个意思就是dajiao必须是一个Person类对应的对象才可以。

3.2.4 函数类型

我们还可以定义一个函数类型,并确定返回值。代码如下:

const Wibus : () => string = () => {
    return "I'm Wibus";
};

我们现在总结一下对象类型可以有几种形式:

  • 对象类型
  • 数组类型
  • 类类型
  • 函数类型

这几种形式我们在TypeScript里叫做对象类型。

Demo3.ts

/**
 * Demo3.ts
 * 基础静态类型和对象类型
 * @Date 2021-1-1
 * @author Wibus
 */


// 基础静态类型
const counter : number = 918;
const myName : string = "Wibus";
// null,undefinde,symbol,boolean,void这些都是最常用的基础数据类型

// 对象类型
// 1 ?
const xiaoGeGe: {
    name: string, //字符串类型
    age: number, //常数类型
  } = {
    name: "Wibus", //对应的数据
    age: 14,
  };
  console.log(xiaoGeGe.name);
// ts-node Demo3.ts Result: Wibus
// 这就是一个经典的对象类型,也是最简单的对象类型。

// 2 ? 对象类型也可以是数组
const xiaoJieJies : string[] = ["Awa", "Qwq", "老婆"]; 
// 这时候的意思是,变量xiaoJieJies必须是一个数组,数组里的内容必须是字符串。你可以试着把字符串改为数字,VSCode会直接给我们报错。
// ⚠️错误示范
const ErrorxiaoJieJies : string[] = [123, "Qwq", "老婆"]; //VSC将会直接报错

// 3 类

class Person {} //定义一个Person类
const Js: Person = new Person(); // 这个意思就是Js必须是一个Person类对应的对象才可以
// 我们还可以定义一个函数类型,并确定返回值
const Wibus : () => string = () => {
    return "I'm Wibus";
};
/**
 * 对象类型可以有几种形式:
 * 对象类型
 * 数组类型
 * 类类型
 * 函数类型
 * 这几种形式在TypeScript里叫做对象类型。
 */

4 Typescript的类型注解和类型推断

TypeScript 中的两个基本概念:类型注解类型推断,这两个概念在我们编写 TypeScript 代码时会一直使用(重点),但很多教程都没有讲解,不过在官方文档中有比较好的解释。你现在可能还不能完全理解我说的这两个概念,但是你看完文章后就会有很直观的了解啦

4.1 type annotation 类型注解

我们直接点,直接看代码

let countDemo4: number;
countDemo4 = 123;

这段代码就是类型注解,意思是显示的告诉代码,我们的count变量就是一个数字类型,这就叫做类型注解

4.2 type inferrence 类型推断

当你明白了类型注解的概念之后,再学类型推断就更简单了,先来看一段代码。还是在Demo4.ts文件中写入下面的代码。

let countInterfence = 123; //let countInterfence: number

这时候我并没有显示的告诉你变量countInference是一个数字类型,但是如果你把鼠标放到变量上时,你会发现 TypeScript 自动把变量注释为了number(数字)类型,也就是说它是有某种推断能力的,通过你的代码 TS 会自动的去尝试分析变量的类型

4.2.1 工作使用问题(潜规则)

  • 如果 TS 能够自动分析变量类型, 我们就什么也不需要做了
  • 如果 TS 无法分析变量类型的话, 我们就需要使用类型注解

4.2.2 不需要注解例子

我们来看两个例子,先是一个不需要注解的

const one = 123;
const two = "abc";
const three = one + two;

4.2.3 需要写的例子

function getTotal(one, two) {
    return one + two;
  }
const total = getTotal(1, 2); //正常情况
const Warningtotal = getTotal(1, "字符串"); //非正常情况

这种形式,就需要用到类型注释了,因为这里的one和two会显示为any类型。这时候如果你传字符串,你的逻辑就是错误的,所以你必须加一个类型注解

function GoodgetTotal(one: number, two: number) {
    return one + two;
  }
const Goodtotal = getTotal(1, 2); 

为什么total这个变量不需要加类型注解,因为当one和two两个变量加上注解后,TypeScript 就可以自动通过类型推断,分析出变量的类型。

4.2.4 推断对象中属性的类型

当然 TypeScript 也可以推断出对象中属性的类型,比如现在写一个小姐姐的对象,然后里边有两个属性。

const XiaoJieJie = {
    name: "刘英",
    age: 18,
  };

/* 
const XiaoJieJie: {
    name: string;
    age: number;
}
 */

写完后你把鼠标放在XiaoJieJie对象上面,就会提示出他里边的属性,这表明 TypeScript 也分析出了对象的属性的类型。

在写 TypeScript 代码的一个重要宗旨就是每个变量,每个对象的属性类型都应该是固定的,如果你推断就让它推断,推断不出来的时候你要进行注释。

Demo4.ts

/**
* Demo4.ts
 * 类型注解 & 类型推断
 * @date 2021-1-1
 * @author Wibus
 * TypeScript 中的两个基本概念:类型注解和类型推断,这两个概念在我们编写 TypeScript 代码时会一直使用(重点)
 */

 // 1 type annotation 类型注解
let countDemo4: number;
countDemo4 = 123;
// 这段代码就是类型注解,意思是显示的告诉代码,我们的count变量就是一个数字类型,这就叫做类型注解

 // 2 type inferrence 类型推断
 let countInterfence = 123; //let countInterfence: number
 /**
  * 我并没有显示的告诉你变量countInference是一个数字类型,但是如果你把鼠标放到变量上时,你会发现 TypeScript 自动把变量注释为了number(数字)类型
  * 也就是说它是有某种推断能力的,通过你的代码 TS 会自动的去尝试分析变量的类型。
  * 这个就彷佛是人的情商比较高,还没等女生表白,你就已经看出她的心思?
  */

  /**
   * 如果 TS 能够自动分析变量类型, 我们就什么也不需要做了
   * 如果 TS 无法分析变量类型的话, 我们就需要使用类型注解
   */

// 3 不需要写注解的例子 ?
const one = 123;
const two = "abc";
const three = one + two;

// 4 需要写的例子 ?
function getTotal(one, two) {
    return one + two;
  }
const total = getTotal(1, 2); //正常情况
const Warningtotal = getTotal(1, "字符串"); //非正常情况
//   这种形式,就需要用到类型注释了,因为这里的one和two会显示为any类型。这时候如果你传字符串,你的逻辑就是错误的,所以你必须加一个类型注解
function GoodgetTotal(one: number, two: number) {
    return one + two;
  }
const Goodtotal = getTotal(1, 2); 
// 为什么total这个变量不需要加类型注解,因为当one和two两个变量加上注解后,TypeScript 就可以自动通过类型推断,分析出变量的类型。

// 5 推断对象中属性的类型
/* 
const XiaoJieJie: {
    name: string;
    age: number;
}
 */
const XiaoJieJie = {
    name: "刘英",
    age: 18,
  };


// 写 TypeScript 代码的一个重要宗旨就是每个变量,每个对象的属性类型都应该是固定的,如果你推断就让它推断,推断不出来的时候你要进行注释。

5 TypeScript 函数参数和返回类型定义

5.1 简单类型定义

上次我们写了一个getTotal的函数,并且对传入的参数作了定义,我们再复习一遍。

新建一个文件demo5.ts,然后写入代码

function LastgetTotal(one: number, two: number) {
    return one + two;
}
const LastTotal = LastgetTotal(1, 2);

代码其实有一个小坑,就是我们并没有定义getTotal的返回值类型,虽然TypeScript可以自己推断出返回值是number类型。 但是如果这时候我们的代码写错了,比如写成了下面这个样子。

function BadgetTotal(one: number, two: number) {
    return one + two + "";
  }
  
const Badtotal = BadgetTotal(1, 2); 

这时候total的值就不是number类型了,但是不会报错。有的小伙伴这时候可能会说,可以直接给total一个类型注解,比如写成这个样子。

const total: number = getTotal(1, 2);

这样写虽然可以让编辑器报错,但是这还不是很高明的算法,因为你没有找到错误的根本,这时错误的根本是getTotal()函数的错误,所以合适的做法是给函数的返回值加上类型注解,代码如下:

function BettergetTotal(one: number, two: number): number {
    return one + two;
}
const BetterTotal = BettergetTotal(1, 2);
//这段代码就比较严谨了

5.2 函数无返回值时定义方法

有时候函数是没有返回值的,比如现在定义一个sayHello的函数,这个函数只是简单的terminal打印,并没有返回值。

function sayHello() {
  console.log("hello world");
}

这就是没有返回值的函数,我们就可以给他一个类型注解void,代表没有任何返回值。

function sayHello(): void {
  console.log("hello world");
}

如果这样定义后,你再加入任何返回值,程序都会报错。

5.3 never 返回值类型

如果一个函数是永远也执行不完的,就可以定义返回值为never,那什么样的函数是永远也执行不完的那?我们先来写一个这样的函数(比如执行执行的时候,抛出了异常,这时候就无法执行完了)。

function errorFuntion(): never {
  throw new Error();
  console.log("Hello World");
}

还有一种是一直循环,也是我们常说的死循环,这样也运行不完,比如下面的代码:

function forNever(): never {
  while (true) {}
  console.log("Hello ");
}

5.4 函数参数为对象(解构)时

这个坑有很多小伙伴掉下去过,就是当一个函数的参数是对象时,我们如何定义参数对象的属性类型。我先写个一般javaScript的写法。

function add({ one, two }) {
  return one + two;
}

const total = add({ one: 1, two: 2 });

在浏览器中你会看到直接报错了,意思是total有可能会是任何类型,那我们要如何给这样的参数加类型注解那?最初你可能会这样写。

function add({ one: number, two: number }) {
  return one + two;
}

const total = add({ one: 1, two: 2 });

你在编辑器中会看到这种写法是完全错误的。那正确的写法应该是这样的。

function add({ one, two }: { one: number, two: number }): number {
  return one + two;
}

const three = add({ one: 1, two: 2 });

如果参数是对象,并且里边只有一个属性时,我们更容易写错。 错误代码如下:

function getNumber({ one }: number) {
  return one;
}

const one = getNumber({ one: 1 });

看着好像没什么问题,但实际这是有问题的,正确的代码应该时这样的。

function getNumber({ one }: { one: number }): number {
  return one;
}

const one = getNumber({ one: 1 });

这样写才是正确的写法

Demo5.ts

/**
 * Demo5.ts
 * TypeScript 函数参数和返回类型定义
 * @date 2021-1-1
 * @author Wibus
 */


// 1 复习下demo4的getTotal
function LastgetTotal(one: number, two: number) {
    return one + two;
}
const LastTotal = LastgetTotal(1, 2);
//  代码其实有一个小坑,就是我们并没有定义getTotal的返回值类型,虽然TypeScript可以自己推断出返回值是number类型。 但是如果这时候我们的代码写错了,比如写程了下面这个样子。
function BadgetTotal(one: number, two: number) {
    return one + two + "";
  }
  
const Badtotal = BadgetTotal(1, 2); //这时候total的值就不是number类型了,但是不会报错。
// 1.1 某些人的解决办法
const SomePersontotal: number = BadgetTotal(1, 2); //这样写虽然可以让编辑器报错(不能将类型“string”分配给类型“number”。),但是这还不是很高明的算法,因为你没有找到错误的根本

// 1.2 合适的做法是给函数的返回值加上类型注解

function BettergetTotal(one: number, two: number): number {
    return one + two;
}
const BetterTotal = BettergetTotal(1, 2);
//这段代码就比较严谨了

// 2 never 返回值类型
// 如果一个函数是永远也执行不完的,就可以定义返回值为never
function errorFuntion(): never {
    throw new Error();
    console.log("Hello World");
}
// ?执行执行的时候,抛出了异常,这时候就无法执行完了

//void 无返回值类型
function avoid(): void {
    const a = 123
    //这个时候这个函数里面是不允许return一个值的
    return a;
}

function forNever(): never {
  while (true) {}
  console.log("Hello");
}
// ⬆️死循环,这样也运行不完

6 TypeScript 中数组类型的定义

TypeScript 中的数组类型,一般的数组类型定义我们已经接触过了,只是没有单独拿出来讲,所以先来复习一下。

6.1 一般数组类型的定义

我们我们先定义一个最最最简单的数组

const numberArr = [1,2,3]'

这时候你把鼠标放在numberArr上面可以看出,这个数组的类型就是 number 类型。这是 TypeScript 通过类型推断自己推断出来的。 如果你要显示的注解,也非常简单,可以写成下面的形式。

const numberArr: number[] = [1, 2, 3];

同样道理,如果你的数组各项是字符串,你就可以写成这样

const stringArr: string[] = ["a", "b", "c"];

也就是说你可以定义任意类型的数组,比如是undefined

const undefinedArr: undefined[] = [undefined, undefined];

这时候问题来了,如果数组中有多种类型,比如既有数字类型,又有字符串的时候。那我们要如何定义那。 很简单,只要加个(),然后在里边加上|就可以了,具体看代码。

const arr: (number | string)[] = [1, "string", 2];

数组简单类型的定义就是这样了,并不难。

6.2 数组中对象类型的定义

在实际项目当中肯定会有对象出现,那这个时候定义的话就麻烦一点点了

const xiaoJieJiesDemo6: {name: string, age: number}[] = [
    {name: "Ming", age: 15},
    {name: "Wibus", age: 14},
];

这样子的形式看起来比较复杂,当然这段程序是可以的,但是不好读嘛

6.2.1 Type Alias 类型别名

TS 为我们弄了一个类型别名,使用type+名字,具体看代码

type lady = {name: string, age: number};

const GoodxiaoJieJiesDemo6: lady[] = [
    {name: "Ming", age: 15},
    {name: "Wibus", age: 14},
];

这样子就好读多了

6.2.2 Class定义

当然,使用class来定义也是可以的,例如这样子

class ladys{
    name: string;
    age: number;
};
const GoodxiaoJieJiesDemo6_2: ladys[] = [
    {name: "Ming", age: 15},
    {name: "Wibus", age: 14},   
];

我们可以看到,是可以的~

Demo6.ts

/**
 * Demo6.ts
 * TypeScript 中数组类型的定义
 * @date 2021-1-2
 * @author wibus
 */

// 1 一般数组类型的定义
const numberArr = [1,2,3];
// 这是最简单的数组类型(number) 当你鼠标放上变量名时,就可以看得出来是number类型(类型推断)
const numberArr2: number[] = [1,2,3];
// 类型注解
const stringArr: string[] = ["a","b","c"];
// 所以说你可以定义任何类型的数组
const undefinedArr: undefined[] = [undefined, undefined];
// 1.1 多种数据类型
const arr: (number|string)[] = [1,2,"abc"];
// 数组简单类型定义就这样了,其实不难

// 2 数组中对象类型的定义
// 在实际项目当中肯定会有对象出现,那这个时候定义的话就麻烦一点点了

const xiaoJieJiesDemo6: {name: string, age: number}[] = [
    {name: "Ming", age: 15},
    {name: "Wibus", age: 14},
];
//这样子的形式看起来比较复杂,当然这段程序是可以的,但是不好读嘛

// 2.1 Type Alias 类型别名

type lady = {name: string, age: number};
const GoodxiaoJieJiesDemo6: lady[] = [
    {name: "Ming", age: 15},
    {name: "Wibus", age: 14},
];
//这样子就好读多了

// 2.2 使用class
// 当然,使用class来定义也是可以的,例如这样子
class ladys{
    name: string;
    age: number;
};
const GoodxiaoJieJiesDemo6_2: ladys[] = [
    {name: "Ming", age: 15},
    {name: "Wibus", age: 14},   
]
// 我们可以看到,是可以的~

7 TypeScript 元组的使用和类型约束

元组是TS特有的一个概念,JavaScript并没有这个概念

7.1 元组的基本使用

我们先来看一个例子

const xiaojiejie = ["Qwq", "NoJob", 28];

这时候把鼠标放到xiaojiejie变量上面,可以看出推断出来的类型。我们就用类型注解的形式给他作一个注解

const zhujie_xiaojiejie : (number | string)[] = ["Qwq", "NoJob", 28];

这个时候你已经添加了类型注解,但是在下面的代码当中会有一个小细节问题

const Badxiaojiejie: (number | string)[] = ["Qwq", 28, "NoJob"];

我们只是把数组的位置调换了一下,但是TS并不能发现这个问题,所以我们需要一个更强大的类型,来解决,所以,这就是元组

元组和数组比较类似,但是类型注解的时候有点不一样

const Shuzu_xiaojiejie: [string, string, number] = ["Qwq", "NoJob", 28];

这个样子我们就把每个类型的位置都固定住了,这就叫做元组

7.2 元组的使用

我们假设有这样子的一组数据

"dajiao", "teacher", 28;
"liuying", "teacher", 18;
"cuihua", "teacher", 25;

如果是这个样子的话,我们可以看到前两个都是字符串,最后一个是常数。

const xiaojiejies: [string, string, number][] = [
  ["dajiao", "teacher", 28],
  ["liuying", "teacher", 18],
  ["cuihua", "teacher", 25],
];

你要搞清楚元组和数组的区别,在理解后能在项目中适当的时候使用不同的类型。

Demo7.ts

/**
 * Demo7.ts
 * TypeScript 元组的使用和类型约束
 * @date 2021-1-2
 * @author Wibus
 */

//元组是TS特有的一个概念,JavaScript并没有这个概念

// 1 元组的基本使用
const xiaojiejie = ["Qwq", "NoJob", 28];
// 把上面的进行类型注解
const zhujie_xiaojiejie : (number | string)[] = ["Qwq", "NoJob", 28];
// 这个时候你已经添加了类型注解,但是在下面的代码当中会有一个小细节问题
const Badxiaojiejie: (number | string)[] = ["Qwq", 28, "NoJob"];
//我们只是把数组的位置调换了一下,但是TS并不能发现这个问题,所以我们需要一个更强大的类型,来解决,所以,这就是元组
const Shuzu_xiaojiejie: [string, string, number] = ["Qwq", "NoJob", 28];
// 这个时候我们就已经把每一个类型都固定住了

// 2 元组的使用

// 得到的数据源是这样子的:
// "dajiao", "teacher", 28;
// "liuying", "teacher", 18;
// "cuihua", "teacher", 25;

const xiaojiejies: [string, string, number][] = [
  ["dajiao", "teacher", 28],
  ["liuying", "teacher", 18],
  ["cuihua", "teacher", 25],
];

// 你要搞清楚元组和数组的区别,在理解后能在项目中适当的时候使用不同的类型。

8 TypeScript 的 interface接口

TypeScript的接口。就是用来规范类型的

8.1 Interface 接口初步了解

我们要作一个简历的自动筛选程序,很简单。年龄小于 25 岁,胸围大于 90 公分的,可以进入面试环节。我们最开始的写法是这样的

const screenresume = (name: string, age: number, bust: number) => {
    age < 24 && bust >= 90 && console.log(name + "Pass");
    age > 24 || (bust < 90 && console.log(name + "Can't Pass"));
};

screenresume("Wibus", 14, 90);

好像还不错,我们再加上个查看简历的功能,于是你的代码是这样

const getResume = (name: string, age: number, bust: number) => {
    console.log(name+ "age: " + age);
    console.log(name+ "Bust: " + bust);
}
getResume("Wibus", 14, 90);

但是这个时候会发现,有很多东西重复了,程序开发中一直强调“代码重用”,两个方法用的类型注解一样,需要作个统一的约束。大上节课我们学了一个类型别名的知识可以解决代码重复的问题,这节课我们就学习一个更常用的语法接口(Interface).

我们可以把这两个重复进行类型注解,定义成统一的接口。代码如下

interface Girl {
  name: string;
  age: number;
  bust: number;
}

有了这个接口后我们程序也要做一些修改

const screenresume2 = (girl: Girl) => {
    girl.age < 24 && girl.bust >= 90 && console.log(girl.name + "Pass");
    girl.age > 24 || (girl.bust < 90 && console.log(girl.name + "Can't Pass"));
};

const getResume2 = (girl: Girl) => {
    console.log(girl.name+ "age: " + girl.age);
    console.log(girl.name+ "Bust: " + girl.bust);
};

const girl = {
    name: "Wibus",
    age: 14,
    bust: 94,
};

screenresume2(girl);
getResume2(girl);

这样就更像是面向对象编程了,以后再用到同样的接口也不怕了,直接使用girl就可以了。

8.2 接口和类型别名的区别

现在我们学了接口,也学过了类型别名,这两个语法和用处好像一样, 确实用起来基本一样,但是也有少许的不同。

类型别名可以直接给类型,比如string,而接口必须代表对象。

比如我们的类型别名可以写出下面的代码:

type Girl1 = stirng;

但是接口就不能这样写,它必须代表的是一个对象,也就是说,你初始化girl的时候,必须写出下面的形式.

const girl = {
  name: "大脚",
  age: 18,
  bust: 94,
};

8.3 接口非必选值定义

我们要求尽量能看到小姐姐的腰围,但是不作强制要求,就是可选值。那接口如何定义?typeScript已经为我们准备好了相应的办法,就是在 : 号前加一个 ?

比如把Girl的接口写成这样。

interface Girl2 {
  name: string;
  age: number;
  bust: number;
  waistline?: number; // 非必选值
}

再修改一下getResume方法,写成这样。

const getResume3 = (girl: Girl2) => {
  console.log(girl.name + "年龄是:" + girl.age);
  console.log(girl.name + "胸围是:" + girl.bust);
  girl.waistline && console.log(girl.name + "腰围是:" + girl.waistline);
};

这时候在定义girl对象的时候,就可以写saistline(腰围),也可以不写了。

Demo8.ts

/**
 * Demo8.ts
 * TypeScript 的 interface 接口
 * @date 2021-1-2
 * @author Wibus
 * interface东西比较多,分两次讲述
 */

// 1 interface 接口初步了解

// 1.1 简历工具
const screenresume = (name: string, age: number, bust: number) => {
    age < 24 && bust >= 90 && console.log(name + "Pass");
    age > 24 || (bust < 90 && console.log(name + "Can't Pass"));
};

screenresume("Wibus", 14, 90);
// ts-node demo8.ts
// 进入面试


const getResume = (name: string, age: number, bust: number) => {
    console.log(name+ "age: " + age);
    console.log(name+ "Bust: " + bust);
}

// 但是似乎name: string, age: number, bust: number一直在出现
// 为了避免啊代码重用,我们可以使用接口

interface Girl {
    name: string;
    age: number;
    bust: number;
}
// 于是我们就需要修改一点程序
const screenresume2 = (girl: Girl) => {
    girl.age < 24 && girl.bust >= 90 && console.log(girl.name + "Pass");
    girl.age > 24 || (girl.bust < 90 && console.log(girl.name + "Can't Pass"));
};

const getResume2 = (girl: Girl) => {
    console.log(girl.name+ "age: " + girl.age);
    console.log(girl.name+ "Bust: " + girl.bust);
};

const girl = {
    name: "Wibus",
    age: 14,
    bust: 94,
};

screenresume2(girl);
getResume2(girl);

// 2 接口和类型别名的区别

type Girl1 = string;

const girl1 = {
    name: "Wibus",
    age: 14,
    bust: 94 
};

// 3 接口非必选值定义

interface Girl2 {
  name: string;
  age: number;
  bust: number;
  waistline?: number; // 非必选值
}

const getResume3 = (girl: Girl2) => {
  console.log(girl.name + "年龄是:" + girl.age);
  console.log(girl.name + "胸围是:" + girl.bust);
  girl.waistline && console.log(girl.name + "腰围是:" + girl.waistline);
};

//这时候在定义girl对象的时候,就可以写saistline(腰围),也可以不写了。

9 TypeScript 的 interface 接口 2

我们接着继续讲接口,接口部分的内容还是比较多的

9.1 允许加入任意值

简历一般是有自由发挥的空间的,所以这时候需要一些任意值,就是自己愿意写什么就写什么。这时候interface接口也是支持的。

interface Girl1 {
    name: string;
    age: number;
    bust: number;
    waistline?: number;
    [propname: string]: any; //属性的名字是字符串类型,属性的值可以是任何类型。
  }

propname 你可以随意改成其他的名字~

这时候我们在对象里给一个性别

const girl = {
    name: "大脚",
    age: 18,
    bust: 94,
    waistline: 21,
    sex: "女",
  };

再修改一下代码,就没有错误了。

const getResume = (girl: Girl1) => {
    console.log(girl.name + "年龄是:" + girl.age);
    console.log(girl.name + "胸围是:" + girl.bust);
    girl.waistline && console.log(girl.name + "腰围是:" + girl.waistline);
    girl.sex && console.log(girl.name + "性别是:" + girl.sex);
  };

这时候我们的程序是不报错的,但是如果我们去掉刚才的设置,就会报错。

[propname:string]:any;  //去掉

9.2 接口里的方法

接口里不仅可以存属性,还可以存方法,比如这时候有个say()方法,返回值是string类型。这时候你就不要再想成简历了,你需要更面向对象化的编程,想象成一个人。

interface Girl2 {
    name: string;
    age: number;
    bust: number;
    waistline?: number;
    [propname: string]: any;
    say(): string;
}

加上这个say()方法后,程序马上就会报错,因为我们对象里没有say方法。那我们就要给对象一个say方法

const girl2 = {
  name: "Wibus",
  age: 14,
  bust: 94,
  waistline: 21,
  sex: "女",
  say() {
    return "欢迎光临 ,红浪漫洗浴!!";
  },
};

9.3 接口和类的约束

JavaScript从ES6里是有类这个概念的,类可以和接口很好的结合

class XiaoJieJie implements Girl {}

这时候类会直接报错,所以我们需要把这个类写的完全点。

class xiaojiejie implements Girl2 {
    name: "大脚";
    age: 18;
    bust: 94;
    waistline: 21;
    sex: "女";
    say() {
      return "欢迎光临 ,红浪漫洗浴!!";
    };
};

9.4 接口的继承

接口也可以用于继承的,比如你新写一个Teacher接口,继承于Person接口。

interface Teacher extends Girl2{
    teach(): string;
}

比如这时候,只看Teacher级别的简历,那我们需要修改getResume()方法。

const getResume2 = (girl2: Teacher) => {
    console.log(girl2.name + "年龄是:" + girl2.age);
    console.log(girl2.name + "胸围是:" + girl2.bust);
    girl2.waistline && console.log(girl2.name + "腰围是:" + girl2.waistline);
    girl2.sex && console.log(girl2.name + "性别是:" + girl2.sex);
};
getResume2(girl2);

修改后,你就会发现下面我们调用getResume2()方法的地方报错了,因为这时候传的值必须有Teach方法,修改girl对象,增加teach()方法,这时候就不会报错了。

const girl2 = {
  name: "Wibus",
  age: 14,
  bust: 94,
  waistline: 21,
  sex: "女",
  say() {
    return "欢迎光临 ,红浪漫洗浴!!";
  },
    teach() {
    return "我是一个老师";
  },
};

关于接口的知识就讲到这里吧,这基本包含了接口 80%的知识

Demo9.ts

/**
 * Demo9.ts
 * TypeScript 的 interface 接口 2
 * @date 2021-1-2
 * @author Wibus
 */

 // 1 允许加入任意值
//  简历一般是有自由发挥的空间的,所以这时候需要一些任意值,就是自己愿意写什么就写什么。这时候interface接口也是支持的。
interface Girl1 {
    name: string;
    age: number;
    bust: number;
    waistline?: number;
    [name: string]: any; //属性的名字是字符串类型,属性的值可以是任何类型。
  }
// 这时候我们在对象里给一个性别
const girl = {
    name: "大脚",
    age: 18,
    bust: 94,
    waistline: 21,
    sex: "女",
  };
//   再修改一下代码,就没有错误了。
const getResume = (girl: Girl1) => {
    console.log(girl.name + "年龄是:" + girl.age);
    console.log(girl.name + "胸围是:" + girl.bust);
    girl.waistline && console.log(girl.name + "腰围是:" + girl.waistline);
    girl.sex && console.log(girl.name + "性别是:" + girl.sex);
  };
// 这时候我们的程序是不报错的,但是如果我们去掉刚才的设置,就会报错。

// 2 接口里的方法
// 接口里不仅可以存属性,还可以存方法,比如这时候有个say()方法,返回值是string类型。这时候你就不要再想成简历了,你需要更面向对象化的编程,想象成一个人。

interface Girl2 {
    name: string;
    age: number;
    bust: number;
    waistline?: number;
    [propname: string]: any;
    say(): string;
  }
// 加上这个say()方法后,程序马上就会报错,因为我们对象里没有 say 方法。那我们就要给对象一个 say 方法

const girl2 = {
  name: "Wibus",
  age: 14,
  bust: 94,
  waistline: 21,
  sex: "女",
  say() {
    return "欢迎光临 ,红浪漫洗浴!!";
  },
    teach() {
    return "我是一个老师";
  },
};

// 3 接口和类的约束
// 类和接口能有很好的结合

class xiaojiejie implements Girl2 {
    name: "大脚";
    age: 18;
    bust: 94;
    waistline: 21;
    sex: "女";
    say() {
      return "欢迎光临 ,红浪漫洗浴!!";
    };
};

// 4 接口的继承
// 接口也可以用于继承的,比如你新写一个Teacher接口,继承于Person接口。

interface Teacher extends Girl2{
    teach(): string;
}

// 比如这时候,只看 Teacher 级别的简历,那我们需要修改getResume()方法。
const getResume2 = (girl2: Teacher) => {
    console.log(girl2.name + "年龄是:" + girl2.age);
    console.log(girl2.name + "胸围是:" + girl2.bust);
    girl2.waistline && console.log(girl2.name + "腰围是:" + girl2.waistline);
    girl2.sex && console.log(girl2.name + "性别是:" + girl2.sex);
  };
  
getResume2(girl2);

10 TypeScript 类的概念和使用

TypeScript 中类的概念和ES6中原生类的概念大部分相同,但是也额外增加了一些新的特性。我在这里会完全从一个新手的角度,讲解类的各项知识点。

10.1 类的基本使用

新建一个文件,叫做demo10.ts,然后定义一个最简单的Lady类,这里要使用关键字class,类里边有姓名属性和一个得到姓名的方法

class Lady {
    content = "Hi,帅哥";
    sayHello() {
      return this.content;
    }
}
  
const goddess = new Lady();
console.log(goddess.sayHello());
// ts-node demo10.ts
//result:Hi,帅哥

代码下面的注释的意思是:运行ts-node demo10.ts命令,得到的结果是:Hi,帅哥

这是一个最简单的类了,如果你有些编程经验,对这个一定很熟悉

10.2 类的继承

TypeScrip 的继承和ES6中的继承是一样的。关键字也是extends,比如我们这里新建一个xiaoJieJie的类,然后继承自Lady类,在XiaoJieJie类里写一个新的方法,叫做sayLove,具体代码如下。

class xiaoJieJie extends Lady{
    sayLove(){
        return "I love you.";
    }
}

const Wibus = new xiaoJieJie();
console.log(Wibus.sayHello());
console.log(Wibus.sayLove());
// ts-node demo10.ts
// result: Hi,帅哥 I love you.

类写好以后,我们声明的对象是xiaoJieJie这个类,我们同时执行sayHello()sayLove()都是可以执行到的,这说明继承起作用了

10.3 类的重写

讲了继承,那就必须继续讲讲重写,重写就是子类可以重新编写父类里边的代码。现在我们在XiaoJieJies这个类里重写父类的sayHello()方法

class XiaoJieJies extends Lady {
    sayLove(){
        return "I love you.";
    }
    sayHello() {
        return "Hi, honey!";
      }
}

const wibus = new XiaoJieJies();
console.log(wibus.sayHello());
console.log(wibus.sayLove());
// ts-node demo10.ts
// result: Hi, honey! I love you.

10.4 super 关键字的使用

比如我们还是想使用Lady类中说的话,但是在后面,加上你好两个字就可以了。这时候就可以使用super关键字,它代表父类中的方法

class Xiaojiejie extends Lady {
    sayLove() {
        return "I love you!";
      }
      sayHello() {
        return super.sayHello() + ".你好!";
      }
}

const goddesss = new Xiaojiejie();
console.log(goddesss.sayHello());
console.log(goddesss.sayLove());
// ts-node demo10.ts
// result: Hi,帅哥.你好!I love you!

那么整个下来呢,我们至少要知道TypoeScript中的类是如何定义和继承的。类中还有很多知识点要讲

Demo10.ts

/**
 * Demo10.ts
 * TypeScript 类的概念和使用
 * @date 2021-1-2
 * @author Wibus
 */

 // TypeScript 中类的概念和 ES6 中原生类的概念大部分相同,但是也额外增加了一些新的特性

// 1 类的基本使用
// 下面是最简单的类
class Lady {
    content = "Hi,帅哥";
    sayHello() {
      return this.content;
    }
}
  
const goddess = new Lady();
console.log(goddess.sayHello());
// ts-node demo10.ts
//result:Hi,帅哥

// 2 类的继承
// TypeScrip 的继承和ES6中的继承是一样的。关键字也是extends,比如我们这里新建一个XiaoJieJie的类,然后继承自Lady类,在XiaoJieJie类里写一个新的方法,叫做sayLove,具体代码如下。

class xiaoJieJie extends Lady{
    sayLove(){
        return "I love you.";
    }
}

const Wibus = new xiaoJieJie();
console.log(Wibus.sayHello());
console.log(Wibus.sayLove());
// ts-node demo10.ts
// result: Hi,帅哥 I love you.

// 类写好以后,我们声明的对象是XiaoJieJie这个类,我们同时执行sayHello()和sayLove()都是可以执行到的,这说明继承起作用了

// 3 类的重写
// 讲了继承,那就必须继续讲讲重写,重写就是子类可以重新编写父类里边的代码。现在我们在XiaoJieJie这个类里重写父类的sayHello()方法

class XiaoJieJies extends Lady {
    sayLove(){
        return "I love you.";
    }
    sayHello() {
        return "Hi, honey!";
      }
}

const wibus = new XiaoJieJies();
console.log(wibus.sayHello());
console.log(wibus.sayLove());
// ts-node demo10.ts
// result: Hi, honey! I love you.

// 4 super 关键字的使用
// 比如我们还是想使用Lady类中说的话,但是在后面,加上你好两个字就可以了。这时候就可以使用super关键字,它代表父类中的方法

class Xiaojiejie extends Lady {
    sayLove() {
        return "I love you!";
      }
      sayHello() {
        return super.sayHello() + ".你好!";
      }
}

const goddesss = new Xiaojiejie();
console.log(goddesss.sayHello());
console.log(goddesss.sayLove());
// ts-node demo10.ts
// result: Hi,帅哥.你好!I love you!

// 我们至少要知道TypoeScript中的类是如何定义和继承的。类中还有很多知识点要讲

11 TypeScript 中类的访问类型

上节已经简单学习了TypeScript中类的使用,这节我们继续学习一下类中的访问类型。其实类的访问类型就是基于三个关键词privateprotectedpublic,也是三种访问类型

11.1 简单的类

我们来写一个简单的类先,我们定义一个 Person 类,然后使用这个类的对象,进行赋值,最后打印在控制台上。

class Person {
    name: string;
  }
  
const person = new Person();
person.name = "Wibus";
  
console.log(person.name);
// ts-node demo11.ts
// result: Wibus

写完后我们直接可以在Terminal(中),输入ts-node demo11.ts进行查看结果,结果会打印出Wibus

11.2 public 访问属性

这时候可以打出Wibus是因为我们如果不在类里对name的访问属性进行定义,那么它就会默认是public访问属性。所以,我们刚刚所写的类相当于这样子

class Person2 {
    public name: string;
};

public从英文字面的解释就是公共的或者说是公众的,在程序里的意思就是允许在类的内部外部被调用.

比如说,我们在类里面写一个sayHello() 方法,访问属性为public

class Person3 {
    public name: string;
    public sayhello(){
        console.log(this.name + ' say hello');
    };
};
// ————————下面的是外部调用——————————
const person3 = new Person3;
person3.name = "Wibus";
person3.sayhello();
// ts-node demo11.ts
// result: Wibus say hello

这是的this.name就是类的内部调用。我们在下面在执行一下这个方法person3.sayHello(), 终端中可以看到一切正常运行了,顺利打印

11.3 private 访问属性

private 访问属性的意思是,只允许在类的内部被调用,外部不允许调用

现在我们把name属性改成private,这时候在类的内部使用不会提示错误,而外部,以及子类使用VSCode直接会报错。

class Person4 {
    private name: string;
    private sayhello(){
        console.log(this.name + ' say hello');
    };
};

class Person_Person4 extends Person4 {
    public saySomeThing() {
        console.log(this.name); 
    }
}
//VSC Error: 属性“name”为私有属性,只能在类“Person4”中访问。
// ————————下面的是外部调用——————————
const person4 = new Person4;
person4.name = "Wibus";
person4.sayhello();
// VSC Error: 属性“sayhello”为私有属性,只能在类“Person4”中访问。

11.4 protected 访问属性

protected 允许在类内及继承的子类中使用

把name的访问属性换成protected,这时候外部调用name的代码会报错,内部的不会报错,和private一样。这时候我们再写一个Person_Person5类,继承于Person4

class Person5 {
    protected name: string;
    protected sayhello(){
        console.log(this.name + ' say hello');
    };
};

class Person_Person5 extends Person5 {
    public sayBye(){
        return "this.name";
    }
}

// ————————外部调用——————————
const person5 = new Person5;
person5.name = "Wibus";
person5.sayhello();
// VSC Error: 属性“sayhello”受保护,只能在类“Person5”及其子类中访问。
// ————————Person_Person5 类 ——————
const person_person5 = new Person_Person5;
person_person5.sayBye(); //VSC 不报错

那么通过这个例子相信你一定知道什么是类的内部和类的外部,也知道了三个访问类型的区别了

Demo11.ts

/**
 * Demo11.ts
 * TypeScript 中类的访问类型
 * @date 2021-1-3
 * @author Wibus
 */

// 上节已经简单学习了TypeScript中类的使用,这节我们继续学习一下类中的访问类型。其实类的访问类型就是基于三个关键词private、protected和public,也是三种访问类型

// 1 简单的类

class Person {
    name: string;
  }
  
const person = new Person();
person.name = "Wibus";
  
console.log(person.name);
// ts-node demo11.ts
// result: Wibus

// 2 public 访问属性
// 这时候可以打出Wibus是因为我们如果不在类里对name的访问属性进行定义,那么它就会默认是public访问属性。

// 相当于
class Person2 {
    public name: string;
};
// public从英文字面的解释就是公共的或者说是公众的,在程序里的意思就是允许在类的内部和外部被调用.
class Person3 {
    public name: string;
    public sayhello(){
        console.log(this.name + ' say hello');
    };
};
// ————————下面的是外部调用——————————
const person3 = new Person3;
person3.name = "Wibus";
person3.sayhello();
// ts-node demo11.ts
// result: Wibus say hello

// 3 private 访问属性
// private 访问属性的意思是,只允许在类的内部被调用,外部不允许调用

class Person4 {
    private name: string;
    private sayhello(){
        console.log(this.name + ' say hello');
    };
};

class Person_Person4 extends Person4 {
    public saySomeThing() {
        console.log(this.name); 
    }
}
//VSC Error: 属性“name”为私有属性,只能在类“Person4”中访问。
// ————————下面的是外部调用——————————
const person4 = new Person4;
person4.name = "Wibus";
person4.sayhello();
// VSC Error: 属性“sayhello”为私有属性,只能在类“Person4”中访问。


// 4 protected 访问属性
// protected 允许在类内及继承的子类中使用

class Person5 {
    protected name: string;
    protected sayhello(){
        console.log(this.name + ' say hello');
    };
};

class Person_Person5 extends Person5 {
    public sayBye(){
        return "this.name";
    }
}

// ————————外部调用——————————
const person5 = new Person5;
person5.name = "Wibus";
person5.sayhello();
// VSC Error: 属性“sayhello”受保护,只能在类“Person5”及其子类中访问。
// ————————Person_Person5 类 ——————
const person_person5 = new Person_Person5;
person_person5.sayBye(); //VSC 不报错

12 TypeScript 类的构造函数

构造函数就是在类被初始化的时候,自动执行的一个方法。我们通过这个构造方法经常作很多需要提前完成的工作,比如显示页面前我们要从后台得到数据

12.1 类的构造函数

简单来说,构造函数的关键字就是constructor

新建一个 Person 类,类的里边定义一个name,但是name我们并不给他值,然后我们希望在new出对象的时候,直接通过传递参数的形式,给name赋值,并打印出来。这时候我们就需要用到构造函数

class Person{
    public name: string;
    constructor(name:string){
        this.name = name;
    };
};

const person = new Person('Wibus');
console.log(person.name);
// ts-node demo12.ts
// result: Wibus

这是最常规和好理解的写法,那么既然都这么说了,就是有更简单的写法啦

12.1.1 简单的写法

class Person2 {
    constructor(public name:string){} // 这个地方的name需要写上访问属性
};

const person2 = new Person2('Wibus2');
console.log(person2.name);

这种写法就相当于你定义了一个name,然后在构造函数里进行了赋值,这是一种简化的语法

12.2 类继承中的构造器写法

普通类的构造器我们已经会了,在子类中使用构造函数需要用super()调用父类的构造函数,如果你看不懂我在说啥的话,看下面的代码

class Teacher extends Person2 {
    constructor(public age: number){
        super('Wibus');
    };
};

const teacher = new Teacher(18);
console.log(teacher.age);
console.log(teacher.name);

如果你不写super('Wibus');的话,VSC将会报错:派生类的构造函数必须包含 "super" 调用。

当然你可以super(''); 直接过去?

父类没有构造函数,子类也要使用super()进行调用,否则就会报错。

class Person3 {};

class Teacher2 extends Person3 {
    constructor(public age: number){
        super();
    };
};

const teacher2 = new Teacher2(18);
console.log(teacher2.age);

这一节主要讲的就是类中的构造函数(也有叫构造器的),构造函数在工作中用的很多,所以你要学会并作充分的练习~

Demo12.ts

/**
 * Demo12.ts
 * TypeScript 类的构造函数
 * @date 2021-1-3
 * @author Wibus
 * 构造函数就是在类被初始化的时候,自动执行的一个方法。我们通过这个构造方法经常作很多需要提前完成的工作,比如显示页面前我们要从后台得到数据
 */

// 1 类的构造函数
//构造函数的关键字是constructor

class Person{
    public name: string;
    constructor(name:string){
        this.name = name;
    };
};

const person = new Person('Wibus');
console.log(person.name);
// ts-node demo12.ts
// result: Wibus
// 这是最常规和好理解的写法

// 1.1 简单的写法

class Person2 {
    constructor(public name:string){} // 这个地方的name需要写上访问属性
};

const person2 = new Person2('Wibus2');
console.log(person2.name);
// 这种写法就相当于你定义了一个name,然后在构造函数里进行了赋值,这是一种简化的语法

// 2 类继承中的构造器写法
// 普通类的构造器我们已经会了,在子类中使用构造函数需要用super()调用父类的构造函数

class Teacher extends Person2 {
    constructor(public age: number){
        super('Wibus');
    };
};

const teacher = new Teacher(18);
console.log(teacher.age);
console.log(teacher.name);

// 父类没有构造函数,子类也要使用super()进行调用,否则就会报错。

class Person3 {};

class Teacher2 extends Person3 {
    constructor(public age: number){
        super();
    };
};

const teacher2 = new Teacher2(18);
console.log(teacher2.age);

// 主要讲的就是类中的构造函数(也有叫构造器的),构造函数在工作中用的很多,所以你要学会并作充分的练习。

13 TypeScript 类的 Getter、Setter 和 static 使用

之前学了类的访问类型private,那这个东西如何使用?其实他的最大用处是封装一个属性,然后通过GetterSetter的形式来访问和修改这个属性。

13.1 类的 Getter 和 Setter

13.1.1 Getter

声明一个XiaoJieJie(小姐姐)类,小姐姐的年龄是不能随便告诉人,使用private,这样别人就都不知道她的真实年龄,而只有她自己知道。

如果别人想知道,就必须通过getter属性知道,注意我这里用的是属性,对他就是一个属性。getter属性的关键字是get,后边跟着类似方法的东西,但是你要注意,它并不是方法,归根到底还是属性。

玄妙就在于getter里,我们可以对_age进行处理,比如别人问的时候我们就偷摸的减少 10 岁。

13.1.2 Setter

\_age是私有的,那类的外部就没办法改变,所以这时候可以用setter属性进行改变

13.2 类中的 static

我们先写一下最常规的写法:

但是现在你不想new出对象,而直接使用这个方法,那TypeScript为你提供了快捷的方式,用static声明的属性和方法,不需要进行声明对象,就可以直接使用

需要注意的是,用static声明的属性和方法,才不需要进行声明对象

14 类的只读属性和抽象类

抽象类很父类很像,都需要继承,但是抽象类里一般都有抽象方法。继承抽象类的类必须实现抽象方法才可以。在讲抽象类之前,还需要讲一下类里的只读属性readonly

14.1 类里的只读属性readonly

新建一个文件Demo14.ts,然后写下面一个类,并进行实例化和赋值操作,代码如下:

class Person {
    constructor(public name:string ){ }
}

const person = new Person('wibus')
console.log(person.name)

写完后我们可以在终端(Terminal)中看一下结果,结果就应该是wibus

比如我现在有一个需求,就是在实例化对象时赋予的名字,以后不能再更改了,也就是我们常说的只读属性。我们先来看现在这种情况是可以随意更改的,比如我写下面的代码。

class Person {
    constructor(public name:string ){ }
}

const person = new Person('wibus')
person.name= 'wibus-wee'
console.log(person.name)

这时候就可以用一个关键词readonly,也就是只读的意思,来修改Person类代码。

class Person {
    public readonly _name :string;
    constructor(name:string ){
        this._name = name;
    }
}

const person = new Person('wibus')
person._name= 'wibus-wee'
console.log(person._name)

这样写完后,VSCode就回直接给我们报错,告诉我们_name属性是只读属性,不能修改

14.2 抽象类的使用

什么是抽象类?举个例子,比如我开了一个洗浴中心,里边有服务员,有初级技师,高级技师,每一个岗位我都写成一个类,那代码就是这样的:

class Waiter {}

class BaseTeacher {}

class seniorTeacher {}

我作为老板,我要求无论是什么职位,都要有独特的技能,比如服务员就是给顾客倒水,初级技师要求会泰式按摩,高级技师要求会 SPA 全身按摩。这是一个硬性要求,但是每个职位的技能有不同,这时候就可以用抽象类来解决问题。

抽象类的关键词是abstract,里边的抽象方法也是abstract开头的,现在我们就写一个Girl的抽象类。

abstract class Girl{
    abstract skill()  //因为没有具体的方法,所以我们这里不写括号

}

有了这个抽象类,三个类就可以继承这个类,然后会要求必须实现skill()方法,代码如下:

abstract class Girl{
    abstract skill()  //因为没有具体的方法,所以我们这里不写括号

}

class Waiter extends Girl{
    skill(){
        console.log('大爷,请喝水!')
    }
}

class BaseTeacher extends Girl{
    skill(){
        console.log('大爷,来个泰式按摩吧!')
    }
}

class seniorTeacher extends Girl{
    skill(){
        console.log('大爷,来个SPA全身按摩吧!')
    }
}

通过这个例子,你也许能对抽象类和抽象方法有一个比较深的认识。其实在实际生产中我们也会把这样的需求用接口interface来实现。

15 配置文件 —— tsconfig.json

应该有些人会看见一些ts项目里面都有一个tsconfig.json这个文件,那这里说下:这个是 TypeScript 的文件,虽然不常用,但是很重要。有必要详细的讲一下这个文件der~

15.1 生成tsconfig.json

这个文件是通过tsc --init命令生成的,在桌面上新建一个文件夹TsDemo,然后打开VSCode,把文件托到编辑器中,然后打开终端Terminal,输入tsc --init

输入完成后,就会出现tsconfig.json文件,你可以打开简单的看一下,不过此时你可能看不懂。

其实它就是用来配置如何对ts文件进行编译的,我们都叫它 typescript 的编译配置文件。

如果此时你的tsc执行不了,很有可能是你没有全局安装 TypeScript,可以全局安装一下。

我在第一篇讲述过安装ts的:https://iucky.cn/posts/code/typescript-1

15.2 让 tsconfig.json 文件生效

你现在可以在文件夹跟目录建立一个demo.ts文件,然后编写一些最简单的代码,代码如下:

const person: string = "wibus";

这时候我们不在使用ts-node直接执行了,需要用tsc demo.ts进行编译,编译后会得到demo.js文件。 生成的代码如下:

var person = "wibus";

这时候好像一切都是正常的,但是我要告诉你的真相是tsconfig.json这个编译配置文件并没有生效。

此时我们打开tsconfig.json文件,找到complilerOptions属性下的removeComments:true选项,把注释去掉。

这个配置项的意思是,编译时不显示注释,也就是编译出来的js文件不显示注释内容。

现在你在文件中加入一些注释,比如:

// I love wibus
const person: string = "wibus";

这时候再运行编译代码tsc demo.ts,编译后打开demo.js文件,你会发现注释依然存在,说明tsconfig.json文件没有起作用。

如果要想编译配置文件起作用,我们可以直接运行tsc命令,这时候tsconfig.json才起作用,可以看到生成的js文件已经不带注释了。

那现在又出现问题了,如果我们的跟目录下有多个ts文件,我们却只想编译其中的一个文件时,如何作?

我们在项目根目录,新建一个文件demo2.ts文件,然后也写一段最简单的 ts 代码。

const person2: string = "jspang.com";

如果这时候我们在终端里运行tsc,虽然tsconfig.json生效了,但是两个文件都被我们编译了。这不是你想要的结果,我们可以用三种办法解决这个问题。

  1. 第一种:使用 include 配置

include属性是用来指定要编译的文件的,比如现在我们只编译demo.ts文件,而不编译demo2.ts文件,就可以这样写。

写配置文件时有个坑需要注意,就是配置文件不支持单引号,所以里边都要使用双引号。

{
  "include":["demo.ts"],
  "compilerOptions": {
      //any something
      //........
  }
}

这时候再编译,就只编译demo.ts文件了。

  1. 第二种:使用 exclude 配置

include是包含的意思,exclude是不包含,除什么文件之外,意思是写再这个属性之外的而文件才进行编译。比如你还是要编译demo.ts文件,这时候的写法就应该是这样了。

{
   "exclude":["demo2.ts"],
  "compilerOptions": {
      //any something
      //........
  }
}

这样写依然只有demo.ts被编译成了js文件。

  1. 第三种:使用 files 配置

files的配置效果和include几乎一样,我是没找出有什么不同,只要配置到里边的文件都可以编译,如果有小伙伴知道有什么不同的,欢迎在视频下方留言,然后一起学习。

{
  "files":["demo.ts"],
  "compilerOptions": {
      //any something
      //........
  }
}

结果是依然只有demo.ts文件被编译。这节课我们就学到这里,目的只是让大家初步了解一下tsconfig.js文件和它的使用方法,文件里边还有很多配置项,这些我们都会逐步讲到。

16 配置文件 —— compilerOptions

compilerOptions配置项,它是告诉TypeScript具体如何编译成js文件的,里边的配置项非常多,我们先来看看几个简单的配置项,目的是让你熟悉compilerOptions的使用方法。

16.1 removeComments 属性

removeCommentscomplerOptions里的一个子属性,它的用处是告诉TypeScript对编译出来的js文件是否显示注释(注解)。比如我们现在把removeComments的值设置为true,就是在js中不显示注释。

我们把上节课文件没有的Demo2.ts和生成的 JS 文件都删除掉,只留Demo.ts文件,然后再Demo.ts文件里,加入一个注释。

// I‘m Wibus
const person: string = "Wibus";

写完注释后,直接再终端Terminal里,输入tsc,输入完成后,很快就会生成一个demo.js文件,打开后会看到下面的代码。

"use strict";
var person = "Wibus";

你写的注释并没有编译到demo.js里。如果我们反之,把removeComments的值,设置为false,这时候demo.js里就会有注释内容了。

"use strict";
// I‘m Wibus
var person = "Wibus";

16.2 strict 属性

strict属性如果设置为true,就代表我们的编译和书写规范,要按照TypeScript最严格的规范来写,如果我们把这个设置为false或者注释掉,意思是我们可以对设置一些不严格的写法。

16.3 noImplicitAny 属性

noImplicitAny属性的作用是,允许你的注解类型 any 不用特意表明,只听概念很难理解。这就是看我视频的一个好处,如果你只看官方 API,你可能要迷糊一阵啥叫允许你的注解类型any不用特意表明,这就是每个汉字我都认识,连在一期就不知道啥意思的最好诠释。

为了更好的说明,我们举个例子,在demo.ts里,删除刚才的代码,然后写一个方法,方法的参数我们设置成任意类型(any)。

function Wibus(name) {
  return name;
}

这时候我们的TypeScript是进行报错的,我们用tsc编译也是报错的。这就是因为我们开启了strict:true,我们先注释掉,然后把noImplicitAny的值设置为false,就不再报错了。

如果设置为noImplicitAny:true,意思就是值就算是 any(任意值),你也要进行类型注释。

function Wibus(name: any) {
  return name;
}

你可以简单的理解为,设置为 true,就是必须明确置顶 any 类型的值。

16.4 strictNullChecks 属性

我们先把strictNullChecks设置为false,它的意思就是,**不强制检查 NULL 类型。**我们举个例子,让你能一下子就明白,还是删除demo.ts里的代码,然后编写代码.

const Wibus: string = null;

代码写完后,你会发现这段代码是不报错的,如果是以前,一定是报错的,这就是我们配置了“不强制检验 null 类型”。如果你设成strictNullChecks:true,这时候就报错了。

16.5 ts-node 的遵循

tsc fileName 是没办法遵循tsconfig.js文件的,那ts-node是否遵循?

这里直接告诉你答案,ts-node是遵循的,感兴趣的可以自行试一下。

这节课我们就是简单的认识一下compilerOptions属性的配置,其实这些你只要掌握方法,并不需要记忆,我也是记不住每一项是干嘛的,用的时候会查 API 就可以了。Next,我们继续学习配置文件

17 配置文件 —— compilerOptions 配置内容详解

我们继续讲complierOptions里的配置项,里边的内容很多,我只能选几个重要的给大家讲讲,然后在这节最后,我会给出大家自己查询的方法。需要再次说明的是,这些配置项没必要记,因为他们真的不是每天都需要用到,所以你只要知道如何配置和重要的几项,学会在自己需要时如何查询就可以了。

rootDir 和 outDir

现在你的js文件直接编译到了根目录下,和ts文件混在了一起。我们当然是不喜欢这种方法的,工作中我们希望打包的js都生成在特定的一个文件夹里,比如build

这时候你就可以通过配置outDir来配置,当然你也可以通过rootDir来指定ts文件的位置,比如我们把所有的 ts 文件都放到 src 下。那配置文件就应该这样写。

{
    "outDir": "./build" ,
    "rootDir": "./src" ,
}

这时候你再在Terminal中输入tsc,就会有不同的效果了。

编译 ES6 语法到 ES5 语法-allowJs

现在你在src目录下用ES6的语法写了一个demo2.js文件,代码如下。

export const name = "wibus";

如果你不做任何配置,这时候试用tsc是没有效果的。你需要到tsconfig.js文件里进行修改,修改的地方有两个。

"target":'es5' ,  // 这一项默认是开启的,你必须要保证它的开启,才能转换成功
"allowJs":true,   // 这个配置项的意思是联通

这两项都开启后,在使用tsc编译时,就会编译js文件了。

sourceMap 属性

如果把sourceMap的注释去掉,在打包的过程中就会给我们生成sourceMap文件.

sourceMap 简单说,Source map 就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。

这里我不对 Source map 文件详细讲解,如果你感兴趣,可以自行百度一下吧。

noUnusedLocals 和 noUnusedParameters

比如现在我们修改demo.ts文件的代码,改为下面的样子。

const wibus: string = null;
export const name = "wibus";

这时候你会发现wibus这个变量没有任何地方使用,但是我们编译的话,它依然会被编译出来,这就是一种资源的浪费。

//编译后的文件
"use strict";
exports.__esModule = true;
exports.name = void 0;
var wibus = null;
exports.name = "wibus";

这时候我们可以开启noUnusedLocals:true,开启后我们的程序会直接给我们提示不能这样编写代码,有没有使用的变量。

noUnusedParameters是针对于名优使用的函数的,方法和noUnusedLocals:true一样,小伙伴们自己尝试吧。

我们讲了几个最常用的方法,如果你需要全面的了解,可以查看这个网址:

https://www.tslang.cn/docs/handbook/compiler-options.html (编译选项详解)

自己进行查看就可以了。

好了配置文件我们就暂时告一段落了,下节课我们讲一下 TypeScript 里的联合类型。

18 联合类型和类型保护

那么我们在这一章节将会学到联合类型和类型保护,需要注意的是,只有联合类型存在的情况下,才需要类型保护。普通的类型注解,并不需要我们这种特殊操作。那先来看一下什么是联合类型。

联合类型展示

所谓联合类型,可以认为一个变量可能有两种或两种以上的类型。用代码举个例子,声明两个接口Waiter(服务员)接口和Teacher(技师)接口,然后在写一个judgeWho(判断是谁)的方法,里边传入一个animal(任意值),这时候可以能是Waiter,也可能是Teacher。所以我们使用了联合类型,关键符号是|(竖线)。

interface Waiter {
  anjiao: boolean;
  say: () => {};
}

interface Teacher {
  anjiao: boolean;
  skill: () => {};
}

function judgeWho(animal: Waiter | Teacher) {}

通过这个简单的例子,你应该知道什么是联合类型了。

function judgeWho(animal: Waiter | Teacher) {
  animal.say();
}

但这时候问题来了,如果我直接写一个这样的方法,就会报错,因为judgeWho不能准确的判断联合类型具体的实例是什么。

这时候就需要再引出一个概念叫做类型保护,类型保护有很多种方法,这节讲几个最常使用的。

类型保护 - 类型断言

类型断言就是通过断言的方式确定传递过来的准确值,比如上面的程序,如果会anjiao(按脚),说明他就是技师,这时候就可以通过断言animal as Teacher,然后直接调用skill方法,程序就不再报错了。同样如果不会按脚,说明就是不同的服务员,这时候调用say()方法,就不会报错了。这就是通过断言的方式进行类型保护。也是最常见的一种类型保护形式。具体看代码:

interface Waiter {
  anjiao: boolean;
  say: () => {};
}

interface Teacher {
  anjiao: boolean;
  skill: () => {};
}

function judgeWho(animal: Waiter | Teacher) {
  if (animal.anjiao) {
    (animal as Teacher).skill();
  }else{
    (animal as Waiter).say();
  }
}

类型保护 - in 语法

我们还经常使用in语法来作类型保护,比如用if来判断animal里有没有skill()方法。

这里你可以赋值上面的judgeWho()方法,然后改一下名字,我这里改成了judgeWhoTwo()方法,具体程序如下:

function judgeWhoTwo(animal: Waiter | Teacher) {
  if ("skill" in animal) {
    animal.skill();
  } else {
    animal.say();
  }
}

这里的else部分能够自动判断,得益于TypeScript的自动判断。

类型保护 - typeof 语法

先来写一个新的add方法,方法接收两个参数,这两个参数可以是数字number也可以是字符串string,如果我们不做任何的类型保护,只是相加,这时候就会报错。代码如下:

function add(first: string | number, second: string | number) {
  return first + second;
}

解决这个问题,就可以直接使用typeof来进行解决。

function add(first: string | number, second: string | number) {
  if (typeof first === "string" || typeof second === "string") {
    return `${first}${second}`;
  }
  return first + second;
}

像上面这样写,就不报错了。这样就可以进行继续开心的编写程序了。

类型保护 - instanceof 语法

比如现在要作类型保护的是一个对象,这时候就可以使用instanceof语法来作。现在先写一个NumberObj的类,代码如下:

class NumberObj {
  count: number;
}

然后我们再写一个addObj的方法,这时候传递过来的参数,可以是任意的object,也可以是NumberObj的实例,然后我们返回相加值,当然不进行类型保护,这段代码一定是错误的。

function addObj(first: object | NumberObj, second: object | NumberObj) {
  return first.count + second.count;
}

报错不要紧,直接使用instanceof语法进行判断一下,就可以解决问题。

function addObj(first: object | NumberObj, second: object | NumberObj) {
  if (first instanceof NumberObj && second instanceof NumberObj) {
    return first.count + second.count;
  }
  return 0;
}

另外要说的是,instanceof 只能用在类上。这节课我介绍四种类型保护的方式,每种方式都在不同场景中使用(还有一些不太常用的类型保护方式,我就不讲了),你需要自己深刻理解,多练习,在开发时才能灵活使用。

Demo18.ts

/**
 * Demo18.ts
 * 联合类型与类型保护
 * @date 2021-2-2
 * @author Wibus
 */

interface waiter{
    anjiao: boolean
    say: () => {};
}
interface teacher{
    anjiao: boolean
    skill: () => {}
}
function judgeWho(animal:waiter | teacher) {
    if (animal.anjiao){
        (animal as waiter).say
    }else{
        (animal as teacher).skill
    }
    if ("skill" in animal){
        animal.skill
    }else{
        animal.say
    }
}
function add(first:string|number, second: string|number) {
    if (typeof first == "string" || typeof second == "string") {
        return `${first}${second}`
    }else{
        return first+second
    }
}

19 Enum 枚举类型

这节主要学一下 TypeScript 中枚举(enum)类型的使用,你如果在程序中能灵活的使用枚举(enum),会让程序有更好的可读性。这里我拿每次去“大宝剑”点餐作个比喻。

一场大保健

比如说我要去做个大保健,那么我需要通过?来随机选择一项服务,下面是三个例子,分别看看那个才是高级的写法

function getServe(status: number) {
  if (status === 0) {
    return "massage";
  } else if (status === 1) {
    return "SPA";
  } else if (status === 2) {
    return "dabaojian";
  }
}
const result = getServe(0);
console.log(`我要去${result}`);
const Status = {
  MASSAGE: 0,
  SPA: 1,
  DABAOJIAN: 2,
};

function getServe(status: any) {
  if (status === Status.MASSAGE) {
    return "massage";
  } else if (status === Status.SPA) {
    return "spa";
  } else if (status === Status.DABAOJIAN) {
    return "dabaojian";
  }
}

const result = getServe(Status.SPA);

console.log(`我要去${result}`);
enum Status {
  MASSAGE,
  SPA,
  DABAOJIAN,
}

function getServe(status: any) {
  if (status === Status.MASSAGE) {
    return "massage";
  } else if (status === Status.SPA) {
    return "spa";
  } else if (status === Status.DABAOJIAN) {
    return "dabaojian";
  }
}

const result = getServe(Status.SPA);

console.log(`我要去${result}`);

那么当然就是最后一个了,请出主角enum

枚举类型的对应值

我们正常人的思路来看的话,你调用时传一个1,并不会输出我要去spa,然鹅

const result = getServe(1);
// 我要去spa

这看起来很神奇,这是因为枚举类型是有对应的数字值的,默认是从 0 开始的。我们直接用console.log()就可以看出来了,这和C语言的数组排序有点类似,C语言的数组也是从0开始数的(其实好多都是啦)

console.log(Status.MASSAGE);
console.log(Status.SPA);
console.log(Status.DABAOJIAN);

可以看出结果就是0,1,2。那这时候不想默认从 0 开始,而是想从 1 开始。可以这样写。

enum Status {
  MASSAGE = 1,
  SPA,
  DABAOJIAN,
}

枚举通过下标反查

我们这里能打印出枚举的值(也有叫下标的),那如果我们知道下标后,也可以通过反差的方法,得到枚举的值。

console.log(Status.MASSAGE, Status[1]);

这样就进行了反查。

20 TypeScript 函数泛型 - 难点

泛型我个人认为是 TypeScript 的一个难点,我第一次学完后根本不能完全理解

联合类型 Demo

先写一个join方法,方法需要接受两个参数: first, second,并且参数有可能是字符串类型,也可能是数字类型

function join (first: string|number, second: string|number) {
  return `${first}${second}`
}
join('I'm', 'wibus')

上面的代码是完全没有问题的,但是现在出现了一个需求,这就first的参数如果是一个字符串的话,要求second也必须要是字符串,反之亦然

那之前所说的都是无法解决的啦,因此就出现了一个东西:generic —— 泛型

初始泛型-generic

泛型的定义使用<>,比如说给我们之前的join方法加一个泛型,名字就叫做type,这个名字是可以随意选择的,但是在实际项目中必须要语义化,毕竟需要做到代码规范我觉得才能算是好代码

function join <type> (first: type, second: type) {
  return `${first}${second}`
}
join<string>('I'm', 'wibus')

如果要是number的话,就只需要join<number>(123, 345)

泛型中数组的使用

如果传递过来的值要求是数字,如何用泛型进行定义那?两种方法,第一种是直接使用[],第二种是使用Array<泛型>。形式不一样,其他的都一样。

第一种写法:

function myFun<ANY>(params: ANY[]) {
  return params;
}
myFun < string > ["123", "456"];

第二种写法:

function myFun<ANY>(params: Array<ANY>) {
  return params;
}
myFun < string > ["123", "456"];

在实际项目中,我们经常使用<T>来作泛型的表示,当然这不是硬性的规定,只是大部分程序员的习惯性写法

多个泛型的定义

一个函数可以定义多个泛型,比如说我们要做俩泛型,第一个用T,第二批P

function join <T, P> (first: T, second: P) {
  return `${first}${second}`
}
join<number, string>(123, 'wibus')

当然也可以使用类型推断

function join <T, P> (first: T, second: P) {
  return `${first}${second}`
}
join(123, 'wibus')
// 自动推断为 number, string

但是我并不建议你在项目中大量使用类型推断吗,这种将不利于阅读

21 TypeScript 类中泛型-难点

在上一节,我们讲了一下TypeScript的函数泛型的基本语法,这次我们来讲类中泛型

编写一个基本类

class SelectGirl {
  constructor(private girls: string[]) {}
  getGirl(index: number): string {
    return this.girls[index];
  }
}

const selectGirl = new SelectGirl(["大脚", "刘英", "晓红"]);
console.log(selectGirl.getGirl(1));

写完后,可以在终端中使用ts-node Demo.ts进行预览,可以看到控制台中输出了刘英的名字。学到现在你写这样的一个类应该是非常容易的了

为什么是刘英,我们的程序一般排序是 0,1,2,3,4,5这样子的,这种排序方式基本所有语言都是这样的

那么如果我们想更好的保护这个名字,将会使用编号,此时上面的代码如果要这样做的话,就会变成

class SelectGirl {
  constructor(private girls: string[] | number[]) {}
  getGirl(index:number): string | number{
    return this.girl[index];
  }
}

看起来很麻烦,因此我们需要使用泛型来重构代码

初始类的泛型

既然书接上回,那么肯定定义泛型是用<>,因此我们可以这个样子

class SelectGirl<T> {
  constructor(private girls: T[]) {}
  getGirl(index: number): T {
    return this.girls[index];
  }
}
const selectGirl = new SelectGirl(["小明", "小红", "小英"])
console.log(selectGirl.getGirl(1));

这时候代码并不报错,也使用了泛型,但是在实例化对象的时候,TypeScript 是通过类型推断出来的。上节课已经介绍,这种方法并不好,所以还是需要在实例化对象的时候,对泛型的值进行确定,比如是string类型,就这样写

const selectGirl = new SelectGirl() <string> ["小明", "小红", "小英"]

泛型中继承

如果我们要求返回的是一个对象中的name,那么将会变成这样:

const this.girls[index].name

这样子的话,代码绝对会报错,传递过来的值必须是一个对象类型的,里面还必须要有一个name属性

这个时候就会用到继承了,我们可以用接口(interface)来实现。写一个Girls的接口,每一个接口中都要有name属性,代码就会变成这样

interface Girls{
  name: string;
}

我们写了接口,就可以用extends才实现泛型继承

class SelectGirl<T extends Girls>{
  
}

那么这样子就会变的这个泛型里面一定要有一个name属性,因为他extends了接口

但是但是,直到现在程序还是报错的,因为我们返回的类型不正确,应该是string才对,所以

interface Girl {
  name: string;
}

class SelectGirl<T extends Girl> {
  // 限制了必须要有 name 属性
  constructor(private girls: T[]) {}
  getGirl(index: number): string {
    return this.girls[index].name;
  }
}

const selectGirl = new SelectGirl([
  { name: "大脚" },
  { name: "刘英" },
  { name: "晓红" },
]);
// 这个时候就不是之前的["aaa","bbb","ccc"]了
console.log(selectGirl.getGirl(1));

我们在SelectGirl中使用了类中泛型,意思是我不知道我之后将会用什么类型但是它永远会限制你有一个约束条件:name属性

泛型约束

这个时候这个泛型是可以是任意类型,布尔、字符串之类的。但是如果要把这个数据类型定为必须是string或者number类型的话

class SelectGirl<T> {
  constructor(private girls: T[]) {}
  getGirl(index: number): T {
    return this.girls[index];
  }
}

const selectGirl = new SelectGirl<string>(["大脚", "刘英", "晓红"]);
console.log(selectGirl.getGirl(1));

然后进行约束,这时候还是使用关键字extends来进行约束,把代码改成下面的样子。

class SelectGirl<T extends string | number> {
  constructor(private girls: T[]) {}
  getGirl(index: number): T {
    return this.girls[index];
  }
}

const selectGirl = new SelectGirl<string>(["大脚", "刘英", "晓红"]);
console.log(selectGirl.getGirl(1));

22 初识命名空间——Namespace

没有命名空间的时候

我们先用类来实现 header, content, footer,类似于template,就像这样:

class Header {
  constructor() {
    const elem = document.createElement("div");
    elem.innerText = "This is Header";
    document.body.appendChild(elem);
  }
}

class Content {
  constructor() {
    const elem = document.createElement("div");
    elem.innerText = "This is Content";
    document.body.appendChild(elem);
  }
}

class Footer {
  constructor() {
    const elem = document.createElement("div");
    elem.innerText = "This is Footer";
    document.body.appendChild(elem);
  }
}

class Page {
  constructor() {
    new Header();
    new Content();
    new Footer();
  }
}

接着使用 tsc 来进行编译,接着放上web来运行一下

虽然虽然这段代码没有任何问题,但是有的人会发现这暴露了过多的全局变量,变得在之后维护会显得一发不可收拾。最好的是,只需要有Page这个全局变量即可,其他的可以不暴露到全局

使用命名空间

命名空间有个关键词:namespace,比如说namespace Home,需要变成全局的类使用export暴露,那么代码将会变成这样:

namespace Home{
  class Header {
    constructor() {
      const elem = document.createElement("div");
      elem.innerText = "This is Header";
      document.body.appendChild(elem);
    }
  }

  class Content {
    constructor() {
      const elem = document.createElement("div");
      elem.innerText = "This is Content";
      document.body.appendChild(elem);
    }
  }

  class Footer {
    constructor() {
      const elem = document.createElement("div");
      elem.innerText = "This is Footer";
      document.body.appendChild(elem);
    }
  }

  export class Page {
    constructor() {
      new Header();
      new Content();
      new Footer();
    }
  }
}

这个时候只会有Home.Page是全局的了