【初学者笔记】🐯年要掌握 Typescript

2,130 阅读13分钟

前言

一尾流莺在此预祝大家虎年大吉 ~ !

6125a237a97901629856311647.png

JavaScript 是弱类型语言, 很多错误只有在运行时才会被发现,而 TypeScript 提供了一套静态检测机制, 可以帮助我们在编译时就发现错误。

随着前端工程化的发展,TypeScript 起到了相当大的作用,可以帮助团队开发避免很多错误的发生。

虽然使用 TypeScript 会让代码量增加,但是会让代码变的更加健壮,更好维护。另外,它的类型推断和语法提示功能属实是可以大幅度提升开发效率的。

比如你写了一个很长的变量,你要么忘了怎么拼的,要么怕写错去复制粘贴,但是有了 TypeScript 你可能只敲了一个字符,整个变量都出来了。

所以说懒才是生产力,这也是我喜欢 TypeScript 的一大原因。

基本类型

可以先 声明变量进行赋值,赋值只可以是 声明时定义的类型,否则会报错

let a: number;
let b: string;
a = 3;//true
a = '3';//false
b = 'hello';//true
b = 5;//false

可以在 声明变量的同时进行赋值 ,赋值只可以是 声明时定义的类型,否则会报错

let c: boolean = false;
c = true;//true
c = 'hello';//false

如果变量的声明和赋值是同时进行的,并且没有定义类型, TS 可以根据声明时的类型自动对变量进行类型检测

  let d = false;
  d = true;//true
  d = 123; //false

可以使用 | 来连接多个类型  (联合类型),变量值只可以是联合类型中的一种

let b: 'male' | 'female';
b = 'male';//true
b = 'female';//true
b = 'hello';  //false

let c: boolean | string;
c = true;//true
c = 'aa';//true
c = 3;//false

不确定 一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里 共有的属性或方法

length 不是 stringnumber 的共有属性,所以会报错。

访问 stringnumber 的共有方法 toString() 是没问题的:

function fn(something: string | number): number {
    return something.length;//false
    return something.toString();//true
}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

第二行的 myFavoriteNumber 被推断成了 string,访问它的 length 属性不会报错。

而第四行的 myFavoriteNumber 被推断成了 number,访问它的 length 属性时就报错了。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // true
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // false

 any 表示任意类型,可以任意赋值一个变量,设置类型为 any 后,相当于对该变量关闭了 TS 的类型检测

可以显式或者隐式的设置类型为 any,只声明,不赋值 ,TS 解析器会自动判断变量类型为 any

任意值上 访问 任何属性 都是允许的,也允许调用 任何方法,可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是 任意值

//显式any
let d: any; 
d = 10;
d = 'aa';
d = false;

//隐式any
let e;   

unknown 表示未知类型的值 实际上就是一个类型安全的 any

let f: unknown;
f = 'hello';

any 类型的变量 可以赋值给任意变量  any 会影响其他变量的类型检测

unknown 不可以赋值给其他类型的变量  否则报错

let a:any;
let b:string;
let c:unknown;
b = a;//true 同时关闭了b的类型检测
b = c;//false

  可以先进行类型检测,再把 unknown 类型的值赋值给其他类型

let s: string; 
if (typeof f === 'string') {
  s = f;
}

类型断言  告诉解析器变量的实际类型 

两种写法

//写法一
s = f as string;
//写法二
s = <string>f;

设置函数返回值的类型

function fn1(): string {
  return 'hello';//true
  return 3;//false
}

void  用来表示空,以函数为例,表示没有返回值的函数

function fn2(): void {
  return;
}

  never 表示永远不会返回结果

function fn3(): never {
  throw new Error('错误了');
}

object 表示一个 js 对象

  let h: object;
  h = {};
  h = function () {}; 

但是 js 中万物皆对象,所以检测对象几乎没有什么意义

主要是为了限制对象中的属性,而不是限制是不是一个对象 

{} 用来指定对象中包含哪些属性,

属性后面加 ? 表示属性是可选属性 [] 表示任意属性

[propName: string] 任意属性名  any 任意类型

  let i: { name: string; age?: number; [propName: string]: any };
  i = { name: 'hzw' };
  i = { name: 'hzw', age: 13 };
  i = { name: 'hzw', sad: 'asd' };

定义函数结构

语法:function fn(形参1:类型,形参2:类型,...)=>类型,参数不符合要求,过多,过少,都会报错

let k: (a: number, b: number) => number;
k = (n1, n2) => {
  return n1 + n2;
};

string[]  表示字符串数组  Array<string>

number[]  表示数值数组   Array<number>

任意类型  Array<any>

let m: string[];
let n: Array<number>;
let o:Array<any>;
m = ['a', 'b', 'c'];
n = [1,2,3];
o = ['a',1]

数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:push 方法只允许传入 number 类型的参数,但是却传了一个 "8" 类型的参数,所以报错了。

let arr: number[] = [1, 1, 2, 3, 5];
arr.push('8');//false

元组  即固定长度的数组,元素过多或者过少都会报错

但是可以数组方法 push 向元组内添加元素,被称之为"越界元素",但是被添加的元素只能是元组中定义的类型,否则报错

let n: [string, number];
n = ['asd', 1];//true
n = ['asd', 1,'cc'];//false
n.push(1)//true
n.push("vv")//true
n.push(true)//false

enum 枚举类型 适用于在几个值当中选择的情况

enum Gender {
  Male,
  Famale,
}
let o: { name: string; gender: Gender };
o = {
  name: 'hzw',
  gender: Gender.Male,
};
console.log(o.gender === Gender.Male); //true

& 表示同时

let p: { name: string } & { age: number };
p = { name: 'hzw', age: 14 };

类型的别名

给类型起一个别名  用 myType 代替 1 | 2 | 3 | 4 | 5

  var z: 1 | 2 | 3 | 4 | 5;
  let l: 1 | 2 | 3 | 4 | 5;
  type myType = 1 | 2 | 3 | 4 | 5;
  let q: myType;
  q = 1;

编译

编译文件

自动编译单个文件

使用 tsc 命令对 TS 文件进行编辑,编译文件时,使用 -w 指令后,TS 编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。

示例:

tsc xxx.ts -w

自动编译整个项目

如果直接使用 tsc 指令,则可以自动将当前项目下的所有 ts 文件编译为 js 文件。

但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件 tsconfig.json

tsconfig.json 是一个 JSON 文件,添加配置文件后,只需 tsc 命令即可完成对整个项目的编译

tsc

tsconfig.json:

文件配置选项

1. include

  • 用来指定哪些目录下的ts文件需要被编译
  • 默认值:["**/*"]
  • ** 表示任意目录  
  •  * 表示任意文件
//表示根目录下src目录下任意目录任意文件
"include": [
  "./src/**/*" 
],

2. exclude

  • 用来指定哪些目录下的 ts 文件不需要被编译
  • 默认值:["node_modules", "bower_components", "jspm_packages"]
//表示不编译根目录下src目录下test目录下任意目录任意文件
"exclude":[
  "./src/test/**/*"  
],

3. extends

  • 用来定义 被继承的配置文件
  • 引入后,当前配置文件中会自动包含引入文件中的所有配置信息
//当前配置文件中会自动包含根目录下base.json中的所有配置信息
"extends": "./base.json",

4. files

  • 用来指定被编译的文件列表,需要把文件一个个列出来比较麻烦,只有需要编译的文件少时才会用到.
 //只会编译根目录下01目录下的hello.ts
"files": ["./01/hello.ts"],

compilerOptions

  • 编译选项是配置文件中非常重要也比较复杂的配置选项
  • compilerOptions 中包含多个子选项,用来完成对编译的配置

1. target

  • 用来指定 TS 被编译为的 ES 版本
  • 可选值:ES3默认)、ES5ES6/ES2015ES7/ES2016ES2017ES2018ES2019ES2020ESNext(最新版本的ES)
//我们所编写的ts代码将会被编译为ES6版本的js代码
"compilerOptions": {
    "target": "ES6"
}

2. module

  • 用来指定要使用的模块化的解决方案
  • 可选值:CommonJSUMDAMDSystemES2020ESNextNone
"compilerOptions": {
    "module": "CommonJS"
}

3. lib

  • 用来指定项目中要使用的库  一般浏览器情况下不需要设置
  • 可选值:ES5ES6/ES2015ES7/ES2016ES2017ES2018ES2019ES2020ESNextDOMWebWorkerScriptHost ......
"compilerOptions": {
    "lib": ["ES6", "DOM"],
}

4. outDir,rootDir

  • outDir 用来指定编译后文件所在的目录
  • rootDir 用来指定代码的根目录
  • 默认情况下,编译后的 js 文件会和 ts 文件位于相同的目录,设置 outDir 后可以改变编译后文件的位置
"compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
}

5. outFile

  • 将编译后的代码合并为一个文件
  • 设置 outFile 后,所有的全局作用域中的代码会合并到同一个文件中
  • 如果 module 制定了 NoneSystemAMD 则会将模块一起合并到文件之中
"compilerOptions": {
    "outFile": "dist/app.js"
}

6.allowJs

  • 是否对 js 文件进行编译,默认为 false

7.checkJs

  • 是否检测 js 代码是否符合语法规范,默认为 false

8.removeComments

  • 是否移除注释,默认为 false

9.noEmit

  • 是否不生成编译后的文件,默认为 false

10.noEmitOnError

  • 是否不生成编译后的文件(当出现错误时),默认为 false
"compilerOptions": {
     "allowJs": true,
     "checkJs": true,
     "removeComments": false,
     "noEmit": false,
     "noEmitOnError": true
}

11.严格检查

  • strict
    • 启用所有的严格检查,默认值为 true,设置后相当于开启了所有的严格检查
  • alwaysStrict
    • 总是以严格模式对代码进行编译
  • noImplicitAny
    • 禁止隐式的 any 类型
  • noImplicitThis
    • 禁止类型不明确的 this
  • strictBindCallApply
    • 严格检查 bindcallapply 的参数列表
  • strictFunctionTypes
    • 严格检查函数的类型
  • strictNullChecks
    • 严格的空值检查
  • strictPropertyInitialization
    • 严格检查属性是否初始化

12.额外检查

  • noFallthroughCasesInSwitch
    • 检查 switch 语句包含正确的 break
  • noImplicitReturns
    • 检查函数没有隐式的返回值
  • noUnusedLocals
    • 检查未使用的局部变量
  • noUnusedParameters
    • 检查未使用的参数
  • allowUnreachableCode
    • 检查不可达代码
    • 可选值:true,忽略不可达代码;false,不可达代码将引起错误

打包

webpack整合

通常情况下,实际开发中我们都需要使用构建工具对代码进行打包;

TS 同样也可以结合构建工具一起使用,下边以 webpack 为例介绍一下如何结合构建工具使用 TS

1.初始化项目

进入项目根目录,执行命令 npm init -y,创建 package.json 文件

2.下载构建工具

npm i  
webpack 
webpack-cli
webpack-dev-server 
typescript 
ts-loader 
clean-webpack-plugin 
html-webpack-plugin
-D

共安装了7个包:

  • webpack:构建工具 webpack
  • webpack-cliwebpack 的命令行工具
  • webpack-dev-serverwebpack 的开发服务器
  • typescriptts 编译器
  • ts-loaderts 加载器,用于在 webpack 中编译 ts 文件
  • html-webpack-pluginwebpackhtml 插件,用来自动创建 html 文件
  • clean-webpack-pluginwebpack 中的清除插件,每次构建都会先清除目录

3.配置webpack

根目录下创建 webpack 的配置文件 webpack.config.js

// 引入处理路径的模块
const path = require('path');
// 引入自动生成html的插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入每次打包之前清除dist目录的插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// webpack所有的配置信息都写在modules.exports中
module.exports = {
  
   optimization:{
       minimize: false // 关闭代码压缩,可选
   },

  // 指定入口文件
  entry: './src/index.ts',

  // 指定打包文件所在的目录
  output: {
    // 指定打包后文件的目录
    path: path.resolve(__dirname, 'dist'),
    // 打包后文件的名字
    filename: 'bundle.js',
    // 禁止使用箭头函数 可选
    environment: {
      arrowFunction: false,
    },
  },

  // 指定webpack打包时要使用的模块
  module: {
    // 指定要加载的规则
    rules: [
      {
        // test指定的是规则生效的文件
        test: /.ts$/,
        // 要使用的loader处理ts文件
        use: [
          'ts-loader',
        ],
        // 要排除的文件
        exclude: /node_modules/,
      },
    ],
  },

  // 配置webpack插件
  plugins: [
    //自动生成html
    new HTMLWebpackPlugin({
      // 自定义title
      title: '自定义title',
      // 配置模板  打包后的html以该文件为模板
      template: './src/index.html',
    }),
    //自动删除dist目录下的文件
    new CleanWebpackPlugin(),
  ],

  // 用来设置哪些文件可以作为模块被引入
  resolve: {
    extensions: ['.ts', '.js'],
  },
};

4.配置TS编译选项

根目录下创建 tsconfig.json,配置可以根据自己需要

{
   "compilerOptions": {
     	 //用来指定TS被编译为的ES版本
       "target": "ES2015",
       //用来指定要使用的模块化的解决方案
       "module": "ES2015",
       //所有严格检查的总开关
       "strict": true
   }
}

5.修改package.json配置

"scripts": {
  //通过webpack进行打包
  "build": "webpack",
  //通过webpack启动网络服务器,并使用谷歌浏览器打开 支持热更新
  "start": "webpack serve --open chrome.exe"
},

6.项目使用

src 下创建 ts 文件,并在并命令行执行 npm run build 对代码进行编译;

或者执行 npm start 来启动开发服务器;

Babel

  • 除了 webpack,开发中还经常需要结合 babel 来对代码进行转换,以使其可以兼容到更多的浏览器
  • 虽然 TS 在编译时也支持代码转换,但是只支持简单的代码转换,对于例如:PromiseES6 特性,TS 无法直接转换,这时还要用到 babel 来做转换;

1.安装依赖包:

npm i -D @babel/core @babel/preset-env babel-loader core-js

共安装了4个包,分别是:

  • @babel/corebabel 的核心工具
  • @babel/preset-envbabel 的预定义环境
  • @babel-loaderbabelwebpack 中的加载器
  • core-jscore-js 用来使老版本的浏览器支持新版 ES 语法

2.修改webpack.config.js配置文件

 // 配置babel
{
  // 指定加载器
  loader: 'babel-loader',
    // 设置babel
    options: {
      // 设置预定义的环境
      presets: [
        [
          // 指定环境插件
          '@babel/preset-env',
          // 配置信息
          {
            // 指定浏览器版本
            targets: {
              chrome: '58',
              ie: '11',
            },
            // 指定corejs版本
            corejs: '3',
            // 使用corejs的方式   表示usage按需加载
            useBuiltIns: 'usage',
          },
        ],
      ],
    },
},

如此一来,使用 ts 编译后的文件将会再次被 babel 处理,使得代码可以在大部分浏览器中直接使用.

接口和泛型

使用 类型声明 来描述一个对象的类型

type myType = {
  name: string;
  age: number;
};
const person1: myType = {
  name: 'hzw',
  age: 18,
};
  • 接口用来定义一个类结构,可以当成类型声明去使用
  • 接口用来定义一个类中应该包含哪些属性和方法
  interface myObiect {
    name: string;
    age: number;
  }
  const person2: myObiect = {
    name: 'hzw',
    age: 18,
  };

类型声明和接口的区别:

类型声明不可以重复写  接口可以重复写,内容会自动合并

  interface myObiect {
    name: string;
  }
 interface myObiect {
    age: number;
  }
  const person2: myObiect = {
    name: 'hzw',
    age: 18,
  };
  • 接口主要负责定义一个类的结构,接口可以去限制一个对象的接口:对象只有包含接口中定义的所有属性和方法时才能匹配接口;
  • 接口只定义结构  不考虑实际值;
  • 接口的作用类似于抽象类,不同点在于:接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法;
interface myClass {
  name: string;
  bark(): void;
}

定义类时,可以使类去实现一个接口 使用 implements 关键字

  class class1 implements myClass {
    name: string;
    constructor(name: string) {
      this.name = name;
    }
    bark() {
      console.log('旺旺旺');
    }
  }

使用接口定义函数的类型

interface ISum {
  (x: number, y: number): number;
}
let add: ISum = (x: number, y: number): number => {
  return x + y;
};

定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定)此时泛型便能够发挥作用;

举个例子,下面这段代码 test 函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的;

由于类型不确定所以参数和返回值均使用了 any,但是很明显这样做是不合适的:

首先使用 any 会关闭 TS 的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型;

function test(arg: any): any{
    return arg;
}

泛型函数

function test<T>(arg: T): T{
    return arg;
}

这里的<T>就是泛型,那么如何使用上边的函数呢?

方式一(直接使用):

使用时可以直接传递参数使用,类型会由 TS 自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式

test(10)
方式二(指定类型):

也可以在函数后手动指定泛型;

test<number>(10)

函数中声明多个泛型

可以同时指定多个泛型,泛型间使用逗号隔开

function test<T, K>(a: T, b: K): K{
  return b;
}

test<number, string>(10, "hello");

泛型类

类中同样可以使用泛型:

class MyClass<T>{
  prop: T;

  constructor(prop: T){
      this.prop = prop;
  }
}

泛型继承

也可以对泛型的范围进行约束

使用 T extends MyInter 表示泛型 T 必须是 MyInter 的子类,不一定非要使用接口类和抽象类同样适用;

interface MyInter{
  length: number;
}

function test<T extends MyInter>(arg: T): number{
  return arg.length;
}

面相对象

定义类

  • 使用 class 关键字来定义一个类
class Person {}
  • 定义实例属性   需要通过对象的实例去访问
class Person {
  name: String = 'hzw';
  age: number = 18;
}
const hzw = new Person();
console.log(hzw.name);//hzw
console.log(hzw.age);18
  • 使用 static 关键字定义静态属性  通过类访问
class Person {
  static age: number = 20;
}
console.log(Person.age);//20
  • 使用 readonly 关键字定义只读属性
  • 可以同时使用 staticreadonly 关键字定义静态只读属性,static 只能在 readonly 前面
class Person {
  readonly color: string = 'red';
  static readonly height: string = '50px';
}
  • 定义实例方法 需要通过对象的实例去访问
class Person {
  sayHello(): void {
    console.log('hello');
  }
}
const hzw = new Person();
hzw.sayHello();//hello
  • 使用 static 关键字定义静态方法  通过类访问
class Person {
  static sayHello(): void {
    console.log('hello,static');
  }
}
Person.sayHello();//hello,static

构造函数

  • 构造函数 会在对象创建时调用
class Dog {
  constructor() {
   console.log("构造函数调用了")
  }
}
const dog1 = new Dog();//构造函数调用了
  • 在构造函数中,可以通过 this 向新建的对象中添加属性
class Dog {
  name: String;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
  • 在实例方法中,this 表示当前的实例,在实例方法中可以通过 this 来表示当前调用方法的对象
class Dog {
  name: String;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  bark(): void {
    //在实例方法中可以通过this来表示当前调用方法的对象
    console.log(this);
  }
}

const dog1 = new Dog('小黑', 3);
dog1.bark();//小黑: Dog {name: "小黑", age: 3}

继承

通过继承可以将多个类中共有的代码写在一个父类中,这样只需要写一次即可让所有的子类都同时拥有父类中的属性

  • 定义一个表示动物的类 Animal
 class Animal {
    name: String;
    age: number;
    constructor(name: string, age: number) {
      this.name = name;
      this.age = age;
    }
    bark(): void {
      console.log(`动物在叫~~`);
    }
  }
  • 定义一个表示狗的类 Dog
  • 使 Dog 类继承 Animal
  • 此时 Animal 被称为父类,Dog 被称为子类
  • 使用继承后,子类将会拥有父类所有的属性和方法
class Dog extends Animal {
}
const dog = new Dog('旺财', 3);
console.log(dog);// Dog {name: "旺财", age: 3}
dog.bark();//动物在叫~~
  • 定义一个表示猫的类 Cat
  • 使 Cat 类继承 Animal
class Cat extends Animal {
}
const cat = new Cat('小白', 4);
console.log(cat);// Cat {name: "小白", age: 4}
cat.bark();//动物在叫~~

子类可以添加自己独有的属性和方法

class Dog extends Animal {
  run() {
    console.log(`${this.name}在跑~~~`);
  }
}
const dog = new Dog('旺财', 3);
dog.run();//旺财在跑~~~

重写

如果子类添加了父类相同的方法,子类方法会覆盖父类,被称之为方法重写

class Dog extends Animal {
	bark(): void {
    console.log('旺旺旺');
  }	
}
const dog = new Dog('旺财', 3);
dog.bark();//旺旺旺

super

在类的方法中,super 表示当前类的父类,在子类中可以使用 super 来完成对父类的引用

 class Animal {
    name: String;
    constructor(name: string) {
      this.name = name;
    }
    bark(): void {
      console.log(`动物在叫~~`);
    }
  }

  //在类的方法中 super表示当前类的父类
  class Dog extends Animal {
    bark() {
      super.bark();
    }
  }
const dog = new Dog('旺财');
dog.bark();//动物在叫~~

子类继承父类时,如果子类中也定义了构造方法,必须调用父类的构造方法!

 //在类的方法中 super表示当前类的父类
  class Dog extends Animal {
    age: number;
    constructor(name: string, age: number) {
      //调用父类的构造函数
      super(name);
      this.age = age;
    }
  }

属性修饰符

抽象类(abstract class)

  • 使用 abstract 开头的类被称为抽象类
  • 抽象类和其他类区别不大,只是不能用来创建对象
  • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
  • 抽象类中可以添加抽象方法
  • 使用 abstract 开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要重写,否则报错
  abstract class Animal {
    name: String;
    constructor(name: string) {
      this.name = name;
    }
    abstract bark(): void;
  }
  class Dog extends Animal {
    bark() {
      console.log('旺旺旺');
    }
  }

属性修饰符

默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在 TS 中可以对属性的权限进行设置

TS 中属性具有三种修饰符:

  • public(默认值),可以在类、子类和对象中修改
  • protected ,可以在类、子类中修改
  • private ,可以在类中修改

public

class Person{
    public name: string; // 写或什么都不写都是public
    public age: number;

    constructor(name: string, age: number){
        this.name = name; // 可以在类中修改
        this.age = age;
    }

    sayHello(){
        console.log(`大家好,我是${this.name}`);
    }
}

class Employee extends Person{
    constructor(name: string, age: number){
        super(name, age);
        this.name = name; //子类中可以修改
    }
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 可以通过对象修改

protected

class Person{
    protected name: string;
    protected age: number;

    constructor(name: string, age: number){
        this.name = name; // 可以修改
        this.age = age;
    }

    sayHello(){
        console.log(`大家好,我是${this.name}`);
    }
}

class Employee extends Person{

    constructor(name: string, age: number){
        super(name, age);
        this.name = name; //子类中可以修改
    }
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
//Property 'name' is protected and only accessible within class 'Person' and its subclasses.

private

class Person{
    private name: string;
    private age: number;

    constructor(name: string, age: number){
        this.name = name; // 可以修改
        this.age = age;
    }

    sayHello(){
        console.log(`大家好,我是${this.name}`);
    }
}

class Employee extends Person{

    constructor(name: string, age: number){
        super(name, age);
        this.name = name; //子类中不能修改
      //Property 'name' is private and only accessible within class 'Person'.
    }
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
//Property 'name' is private and only accessible within class 'Person'.

属性存取器

  • 对于一些不希望被任意修改的属性,可以将其设置为 private,直接将其设置为 private 将导致无法再通过对象修改其中的属性
  • 我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
  • 读取属性的方法叫做 setter 方法,设置属性的方法叫做 getter 方法
class Person{
    private _name: string;

    constructor(name: string){
        this._name = name;
    }
		//get 属性名
    get name(){
        return this._name;
    }
		//set 属性名
    set name(name: string){
        this._name = name;
    }

}

const p1 = new Person('孙悟空');
// 实际通过调用getter方法读取name属性
console.log(p1.name);
// 实际通过调用setter方法修改name属性 
p1.name = '猪八戒'; 

简写

  • 在参数中加修饰符可以达到简写的目的
  • 不用声明 name:string
  • 构造函数中不用 this.name=name
class Person{
    constructor(public name: string){
    }
}
//等价于
class Person {
    name: string;
    constructor(name: string) {
    this.name = name;
    }
}

后语

到这为止,你就可以使用 ts 进行初步的开发了。但是对于 ts 来说,掌握这些是远远不够的。

再给大家推荐两个学习 ts 的网站。

👉👉 TypeScript 入门教程

👉👉 深入理解 TypeScript