TypeScript

时间:2022-12-25 08:53:11

现在说起TypeScript想必大家都不会陌生的,当初从碎片信息中了解TypeScript,我认为他的变量声明和Rust语言有几分相似,是一门比较严格的语言,今天正式的来学习他

JavaScript易学习,易用,以至于大多数人对于JavaScript的底层理解不会那么注重,所以就会经常出现开发时没发现错误,生产环境哐哐出问题,维护成本太高,所以TypeScript出现了,它是以JavaScript为基础构建的语言,也就是JavaScript的一个超集,可以在支持JavaScript的运行时中执行,但是不能在JavaScript解析器中直接执行,在执行前需要进行编译为JavaScript进行运行

这篇文章将在JavaScript的基础上学习TypeScript ,并在TypeScript的基础上理解 JavaScript,在二者交辉相映之下进行项目构建(WebPack + Babel)理解平稳退化与渐进增强,打通任督二脉之后进行高级语法以及高级编程思想的理解以及实际使用 let’s Go


TypeScript开发环境搭建

我们使用 npm 全局安装 typescript 就可以了
npm i -g typescript
这句命令下载的是typescript的编译器

安装完成之后可以通过 tsc 来查看是否安装完成

TypeScript


TypeScript新增数据类型

对于TS的数据类型,第一个点要说的还是一个变量的声明,在TS中声明变量是需要指定其类型的,当然如果你没有手动指定变量数据类型,那么TS会在声明变量且赋值的时候会自动进行一个数据类型的推断

let num: number = 666

// 参数严格要求number类型以及返回数据类型以及要求参数个数
function func(a: number, b: number): number {
	return a + b
}

// 并且参数要求也是比较严格的
func(a:666,b:666)
// 使用字面量进行变量类型声明

let a: 10
a = 10
// a = 20 错误

/**
 * 联合类型
 */
let b: "str1" | "str2"
b = "str1"
b = "str2"
// b = "str3" 错误

let c: boolean | number | string
c = true
c = 66
c = "str66"
// c = [6,5,4,3,2,1] 错误

当然,既然有 | ,那么也是应该可以使用 & 的,不过我们代入代码中看看。既是一种类型还是另一种类型,先不要管有没有意义,这个本身就是错的,不过还真能这么写,我们还会用到,比如这个场景下

你可能也想到了,就是多条件限制的时候:

let testProp: { name: string } & { age: number }

除了以上的JavaScript基本的数据类型以及typescript的骚操作之外,typescript也引入的自己的一些数据类型

// 随意赋值(任意数据类型)
let d: any

any类型不常用,如果当typescript没有推测出来变量数据类型的话,那么就会自动为变量判断为any类型,并且any类型还有一个所谓的连锁效应

/**
 * any 与 unknown
 */

let testV1: any
testV1 = true

let testV2: unknown
testV2 = 666

// 不报错,any 类型已经影响到其赋值变量上了
let testV3: string = testV1

// 报错, unknown 不允许
// let testV4: boolean = testV2

意思就是我们可以将一直的所有类型给到其他变量,但是不能将未知的所有类型给到其他变量

所以就有这么一个问题,就算unknown的数值字面量是boolean类型的,那么也不能直接给到其他变量,唯一可以解决这个问题的就是使用一个类型判断进行一个包裹

/**
 * any 与 unknown
 */

let testV1: any
testV1 = true

let testV2: unknown
testV2 = false

// 不报错,any 类型已经影响到其赋值变量上了
let testV3: string = testV1

if (typeof testV2 === "boolean") {
	let testV4: boolean = testV2
}

当然还有一种办法,使用typescript的类型断言语法

let testV4: boolean = testV2 as boolean 或者 let testV4: boolean = <boolean>testV2

紧接着就是针对于函数返回值的两种新增数据类型了,void 以及 never

void 用来表示空,但并不是绝对性的没有返回值的意思,你可以不返回值,也可以返回一个空,比如 undefined,而never 表示绝对性的不会返回结果,我们可以在其中报警告、报错、停止业务向下进行,但是严格要求不能有返回值

const Func = (): void => {
	return
}

const Func2 = (): never => {
	throw new Error("出现问题,业务*终止")
}

其实没有必要用箭头函数,我只是想在新拓展性语言中去玩一玩

接下来就是复杂数据类型的重头戏了,也就是对象以及数组的骚操作

关于对象的话,我们可能会想到,不就和上面的简单数据类型一样嘛,直接给到对象一个数据类型就可以了,其实并没有那么简单,因为有这样一句耳熟能详的话:万物皆对象,也就是说我们给到一个空对象也行,给到一个函数也行,那么这个数据类型限制有什么作用呢?其实正是这里让我真正体验到了typescript的美妙

// 想象中的写法
let obj: object
obj = {}
obj = function() {}

// 现实中优雅的写法
let testObj: { name: string }
testObj = { name: "Str1" }

// 可选属性的运用
let testObj2: { name: string; age?: number }
testObj2 = { name: "hello" }

// 除指定属性外的所有其他属性
let testObj3: { name: string; [propName: string]: any }
testObj3 = { name: "hello", age: 666, hobby: ["足球", "篮球", "跑步"] }

这个我真的是爱了,太优雅了

当然对于函数我们也是可以这样去写的,定义函数结构

let Func: (num1: number, num2?: number) => number

Func = (num1: number): number => {
	return num1
}

还有就是数组,数组比较简单,在实际项目开发中我们需要一个数组中的数据的数据类型统一限制,这时数组的数据类型的结构定义就有用多了,同样是大大提升了性能

let arr: Array<number>
let arr2: Array<string>

let arr3: string[]
let arr4: number[]

这俩种写法都是可以的

接下来就是 ts 新增的一个数据类型了,叫做元组,在Rust种也是有元组这个概念的,不过它们是有本质区别的,Rust中的元组定义的是字节而不是简单的数据类型,我喜欢叫他 Tuple,正是有了元组才完善了数组这一数据类型,也就是说 Tuple 一般用来存放少量的、固定的、不同数据类型的数值,而 array 用来存放相对大量的、同数据类型数值

let tup: [string, number, boolean]

tup = ["Hello", 2023, true]

还有最后一种 ts 新增的数据类型,叫做 枚举(enum),也就是使用枚举类将所有可能的情况都列举出来,然后供我们去使用

/**
 * 枚举
 */

// 定义枚举
enum Sex {
	Man,
	Woman,
}

// 在业务下使用枚举
let user: { name: string; age?: number; sex: Sex }

user = {
	name: "Brave-AirPig",
	age: 21,
	sex: Sex.Man,
}

// 判断是否为男性
console.log(user.sex === Sex.Man)

其实还是很有趣的,我们可以看一下编译成功之后的JavaScript文件,居然是一个闭包

/**
 * 枚举
 */
// 定义枚举
var Sex;
(function (Sex) {
    Sex[Sex["Man"] = 0] = "Man";
    Sex[Sex["Woman"] = 1] = "Woman";
})(Sex || (Sex = {}));
// 在业务下使用枚举
var user;
user = {
    name: "Brave-AirPig",
    age: 21,
    sex: Sex.Man
};
// 判断是否为男性
console.log(user.sex === Sex.Man);

数据类型就到这里,还有一个 ts 小技巧,提高复用性,便于后期维护,我们可以给类型起别名

等等,就像下面这样吗?那这有什么意义呢?

type MyType = number

let str: MyType = 66

我们在开发中难免会遇到自定义类型不是吗,那么这一切就都说的通了

type MyType = 66 | 99 | 999

let str: MyType = 66

TypeScript怎么做到优雅的配置编译

自动化监视单个文件变化,这个概念相信大家都不陌生,就是那种热更新功能,在做前端页面开发的时候可谓是必不可少的一项工具,不过话题又回来了,这个方法只能监视单个文件,如果我们还有其他的 ts 文件,那么是无法进行多文件监视的 tsc 文件 -w 也就是第一次编译的时候加上一个 -w,不过我们立马会想到一种方法,咱们一个文件开一个命令窗不就行了吗,哈哈哈

我只是卖了一个关子,其实一定是有那样一个近乎完美的解决方案的

我们可以在项目根目录下创建一个 ts 的配置文件,命名为:tsconfig.json (不同于其他JSON文件的是,该文件可以写注释)

然后我们使用命令行 tsc 直接监控项目下所有 ts文件

常用配置:

include : 指定被编译的 ts 文件

exclude : 指定不被编译的 ts 文件

extends : 继承其他 JSON 文件中的配置

files : 单个设置需要被编译的文件

{
	"include": [
		// src 目录下的任意目录(**代表任意目录 *代表任意文件)
		"./src/**/*"
	],
	"exclude": ["./src/myPublicTest/**/*"],
	// 继承其他 JSON 文件中的配置
	"extends": "./config/myConfig",
	// 一个一个去设置需要被编译的文件,类似于include
	"files": ["xx.ts", "yx.ts", "yy.ts", "xy.ts"]
}

重头戏 - 编译器选项

compilerOptions 指向一个对象,其中包含了很多的配置选项

target: "ES6" : 将 ts 文件编译为 es6 版本的JavaScript 代码

当然,我们知道如今不止 ES6 了,ES6笼统的说的话那就是JavaScript全新版本,准确的来说 ES6 是 ES2015,之后是还有很多版本的,所以在 VSCode 中给到我这样一种提示,我们只能按规定这样写版本(ESNext表示最新版本)
TypeScript

module : 指定我们要使用的模块化规范,比如 ES6 的模块化,Node中我们熟知的Commonjs,以及AMD,UMD,以及更多更多,所以我们可以在这里玩上一段时间,看看所有模块规范下使用 JavaScript 会怎么写,这很有趣

let tup: [number, boolean] = [66, true]

export default { tup }
import tup from "./test_02"
console.log("test_01")

console.log(`导入的test_02的内容 ${tup}`)

编译完成之后的 js 文件(使用commonjs模块化规范)

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const test_02_1 = require("./test_02");
console.log("test_01");
console.log(`导入的test_02的内容 ${test_02_1.default}`);

lib (数组) : 依赖库,我们一般不改,在浏览器运行时中我们选择 dom ,在 JavaScript 不同运行时中需要使用对应依赖库(默认浏览器环境依赖)

outDir : 指定编译后文件指定目录

outFile : 将多个文件合并为一个文件(全局作用域所有代码),这里需要注意的是,如果多个文件中使用模块化导入导出的话,也就是多个文件相关连,那么必须使用 AMD模块化规范或者 System 模块化规范,像ES6,CommonJS这样的模块化规范是会出错的

allowJs : ( Boolean )是否对 js 文件进行编译

checkJs : ( Boolean )是否检查 js 代码是否符合语法规范

removeComments : ( Boolean )是否移除注释

noEmit : ( Boolean )不生成编译后的文件

noEmitOnError : ( Boolean )只有代码有错误的时候才不会生成编译后的文件

{
	"compilerOptions": {
		"target": "ESNext",
		"module": "commonjs",
		"lib": ["ES2015.Promise"],
		"outDir": "./dist",
		"allowJs": true,
		"checkJs": true,
		"removeComments": true,
		"noEmitOnError": true
	}
}

语法检查配置

同样是在 compilerOptions 配置对象下的一些属性

alwaysStrict : 编译后的 剪视文件是否开启严格模式

noImplicitAny : 是否禁止隐式的 any 类型

noImplicitThis : 是否禁止不明确类型的 this指向 的使用

strictNullChecks : 严格检查空值

strict : 所有严格检查的总开关

{
	"compilerOptions": {
		"strict": true,
		"target": "ESNext",
		"module": "commonjs",
		"lib": ["ES2015.Promise"],
		"outDir": "./dist",
		"allowJs": true,
		"checkJs": true,
		"removeComments": true,
		"noEmitOnError": true,
		"alwaysStrict": true,
		"noImplicitAny": true,
		"noImplicitThis": true,
		"strictNullChecks": true
	}
}

查看更多配置

WebPack 与 TypeScript

我们在项目开发中一定会使用种种打包工具去统一处理 ts 文件,而不是刚才的小打小闹,我这里选择使用 webpack 来进行配置

首先我们初始化项目:npm init

然后安装开发依赖(-D):webpack打包工具、webpack-cli命令行工具、typescript包、ts-loader(整合ts以及webpack的加载器)

然后我们创建一个 webpack 的配置文件 webpack.config.js

TypeScript

然后我们再创建一个用来指定 ts 文件编译规范的配置文件,这个我们之前配置过,可以看到上面的图示中也是有这样一个配置文件的

最后我们在 package,json 配置文件中写入使用 bulid 命令 运行 webpack

{
	"name": "typescript",
	"version": "1.0.0",
	"description": "",
	"main": "index.js",
	"scripts": {
		"dev": "webpack --mode development",
		"build": "webpack --mode production"
	},
	"author": "",
	"license": "ISC",
	"devDependencies": {
		"ts-loader": "^9.4.2",
		"typescript": "^4.9.4",
		"webpack": "^5.75.0",
		"webpack-cli": "^5.0.1"
	}
}

最后直接项目根目录运行 npm run build 就可以完成这样的项目打包了,我写了一个很没有用的函数,然后调用打印了出来,没想到编译完…

TypeScript

直接一个自执行函数打印了一下结果 ????????????

接下来如果我们想将代码运行在浏览器运行时中,那么就需要一个 html文件 来做支撑,不过我们不去手动创建或者编写,而是再次引入依赖 webpack 的一个库,html-webpack-plugin 安装这个就可以了
然后在 webpack 配置文件中引入我们刚才安装好的插件,并在 plugins 节点下将插件实例放上去

// 引入 node 中的 path 库
const path = require("path")

// HTML 文件
const HTMLWebpackPlugin = require("html-webpack-plugin")

module.exports = {
	/**
	 * webpack 中的配置信息写入该对象
	 */

	// 开发模式
	mode: "production",

	// 入口文件
	entry: "./src/index.ts",

	// 打包文件所在目录
	output: {
		// 指定打包文件目录
		path: path.resolve(__dirname, "dist"),
		// 打包后文件的名字
		filename: "bundle.js",
	},

	// webpack 打包所使用的模块
	module: {
		// 指定加载规则
		rules: [
			{
				// 指定规则生效的文件(使用正则包含所有含有.ts的文件)
				test: /\.ts$/,
				// 使用 ts-loader 来处理这些文件
				use: "ts-loader",
				// 排除的文件
				exclude: /node_moudles/,
			},
		],
	},
	plugins: [new HTMLWebpackPlugin()],
}

我们可以看到我们的HTML文件就自动生成了,酷!

TypeScript

当然,对于 html 的结构我们可以自己设置,就是在创建实例的时候传入一个对象进行设置,比如我们可以写 title:xxxxxx,不过这个总归是不现实的,我们想要自己写一个 html 结构,然后让 webpack 去用

plugins: [
	new HTMLWebpackPlugin({
		template: "./src/index.html",
	}),
]

关于运行的话,我们如果是VSCode的话,可以直接使用一个叫做 LiveServer 这样一个插件来在本地开启一个端口运行我们的项目,如果使用的是其他的开发软件的话,可以考虑再次让 webpack 来协助我们,我们再安装一个叫做 webpack-dev-server 的依赖,它可以将我们的项目跑在服务器上,实现一个热更新这样一个功能

安装完毕之后在全局 package.json 文件中进行一个配置,来使用命令打开服务器并运行在浏览器中

{
	"name": "typescript",
	"version": "1.0.0",
	"description": "",
	"main": "index.js",
	"scripts": {
		"dev": "webpack --mode development",
		"build": "webpack --mode production",
		"start": "webpack serve"
	},
	"author": "",
	"license": "ISC",
	"devDependencies": {
		"html-webpack-plugin": "^5.5.0",
		"ts-loader": "^9.4.2",
		"typescript": "^4.9.4",
		"webpack": "^5.75.0",
		"webpack-cli": "^5.0.1",
		"webpack-dev-server": "^4.11.1"
	}
}

TypeScript

成功 ????

接着我们在引入一个库,用来在每次更新文件的时候,清除之前的文件夹下的文件,并不是简单的更新,如果我们在项目开发时有一个文件不再被需要了,那么这个功能就可以避免人为失误所照成的代码冗余 , 安装 clean-webpack-plugin,然后还是引入 webpack 配置文件,然后创建实例

当然,现在我们可以在 output 节点之下书写 clean:true 来解决这个问题

TypeScript
最后阐述一个比较重要的配置项,也是 webpack 的一个配置,resolve 用来告知 webpack 哪些文件可以作为模块来进行导入使用,这是很重要的

// 引入 node 中的 path 库
const path = require("path")

// HTML 文件
const HTMLWebpackPlugin = require("html-webpack-plugin")

module.exports = {
	/**
	 * webpack 中的配置信息写入该对象
	 */

	// 开发模式
	mode: "production",

	// 入口文件
	entry: "./src/index.ts",

	// 打包文件所在目录
	output: {
		// 指定打包文件目录
		path: path.resolve(__dirname, "dist"),
		// 打包后文件的名字
		filename: "bundle.js",
		// 更新删除老旧文件
		clean: true,
	},

	// webpack 打包所使用的模块
	module: {
		// 指定加载规则
		rules: [
			{
				// 指定规则生效的文件(使用正则包含所有含有.ts的文件)
				test: /\.ts$/,
				// 使用 ts-loader 来处理这些文件
				use: "ts-loader",
				// 排除的文件
				exclude: /node_moudles/,
			},
		],
	},
	plugins: [
		new HTMLWebpackPlugin({
			template: "./src/index.html",
		}),
	],
	// 设置引用模块
	resolve: {
		// 以ts、js 为后缀的文件都可以作为模块来使用
		extensions: ["ts", "js"],
	},
}

Babel

ts只能做一些简单的语法转换,想要更好的兼容性并且实现高级语法的一些转换,那么就需要用到 Babel 这个工具了,做前端的大家应该不会陌生这个工具,在使用之前我们需要去安装一些包,原来前端不是被代码折磨的,而是这些环境配置以及包管理 ????

@babel/core
@babel/preset-env
babel-loader
core-js

好了,就这四个包,关于core的环境是比较大的,所以到时候我们按需加载使用即可,比如我们想要去使用一些非常高级的语法,但是用户在 ie 上,那就完蛋了,所以 core 就是解决这个问题的

安装完成之后我们可以在 webpack 配置文件中进行配置,首先就是添加一个加载器,在 ts加载器的基础上再加上一个babel加载器,因为规则是从下往上执行的,所以将babel的加载器写到上面比较好

// 引入 node 中的 path 库
const path = require("path")

// HTML 文件
const HTMLWebpackPlugin = require("html-webpack-plugin")

module.exports = {
	/**
	 * webpack 中的配置信息写入该对象
	 */

	// 开发模式
	mode: "production",

	// 入口文件
	entry: "./src/index.ts",

	// 打包文件所在目录
	output: {
		// 指定打包文件目录
		path: path.resolve(__dirname, "dist"),
		// 打包后文件的名字
		filename: "bundle.js",
		// 更新删除老旧文件
		clean: true,
	},

	// webpack 打包所使用的模块
	module: {
		// 指定加载规则
		rules: [
			{
				// 指定规则生效的文件(使用正则包含所有含有.ts的文件)
				test: /\.ts$/,
				// 使用 ts-loader 来处理这些文件
				use: [
					// 配置babel
					{
						// 指定加载器
						loader: "babel-loader",
						options: {
							// 预定义假设环境
							presets: [
								[
									// 指定环境插件
									"@babel/preset-env",
									// 配置信息
									{
										targets: {
											// 兼容 chrome 浏览器 66 版本以及 ie浏览器 8 版本
											chrome: "66",
											ie: "8",
										},
										// core 版本
										corejs: "3",
										// 使用 coreJs 的方式 -- usage->按需加载
										useBuiltIns: "usage",
									},
								],
							],
						},
					},
					"ts-loader",
				],
				// 排除的文件
				exclude: /node_moudles/,
			},
		],
	},
	plugins: [
		new HTMLWebpackPlugin({
			template: "./src/index.html",
		}),
	],
	// 设置引用模块
	resolve: {
		// 以ts、js 为后缀的文件都可以作为模块来使用
		extensions: ["ts", "js"],
	},
}

TypeScriprt 高级语法

面向对象

面向对象其实是一种编程概念,我之前一直不是很明白,尤其是 JavaScript 这门语言,真的是一切皆对象,所以当时很容易就将这种语言实质性理论与面向对象思想所混淆了

直到了解到 ES6 的类,通过类和方法的比较中我才明白这种思想,当然函数编程依然可以使用面向对象这种思想

万物皆对象,或者你可以将面向对象编程看做是生产一辆电动车,有那么一个将电动车必须的零件封装成为一个整体的类,我们生产不同功能,不同样式的电动车都可以使用这个类,那么就大大节省了生产成本,这就是面向对象

与之相对的就是面向过程编程,面向过程的话就是按照生产电动车的步骤,一步一步的去构建,最终完成产品的构建,但是我们想要生产一辆增加功能,样式改变的其他电动车,那么没办法,只能重新再构建,如果想在之前的样式上改,那么会更麻烦,可能付出更大的成本

所以万物皆对象说的就是什么都可以按照这种方法去做,去生产,世间万物都可以这样被创造出来并被使用类模板增加功能样式快速的被二创、三创…

当然,这只是在使用中的理解,面向对象依旧离不开抽象这个概念,将一个具体的事物抽象为一个不是那么具体的架构,将上面思想结合起来融会贯通即是面向对象

如果我们还是没理解,那么我们可以想象一下我们为什么可以去操作浏览器,是,我们是使用的 JavaScript 去操作的,但是为什么浏览器可以被操作???

就是因为浏览器将自己的实际功能抽象为一个个方法,一个个 API,结合起来起了一个名字叫做 DOM,也就是文档对象模型,所以我们操作的是浏览器为我们抽象出来的 DOM API,而不是直接操作的浏览器

这就是抽象 ????

并不难,理解了就很简单了

接下来我们实践使用 ts 创建类,来理解抽象对象

TypeScript
和 JavaScript 中的类的使用方法是一样的,也就是多出一些类型声明,我们直接看代码理解即可

class MyClass {
  name: string;

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

  // 定义实例属性(需要实例化对象才可以访问到该属性) - 可读可写
  // name: string = "Brave-AirPig";
  age: number = 21;

  // 只读属性
  readonly nick_name: string = "AirPig";

  // 静态属性(可以直接 . 获取到类的属性)
  static hobby: Array<string> = ["足球", "跑步", "篮球"];

  // 方法
  say() {
    console.log(`${this.name}说了一句话`);
  }

  // 类方法
  static sayHello() {
    console.log("Hello");
  }
}

const people1 = new MyClass("Brave-AirPig");
const people2 = new MyClass("Bun");

console.log(people1);
console.log(people1.name);
console.log(MyClass.hobby);

people1.say();
MyClass.sayHello();

console.log(people2);

// o { age: 21, nick_name: 'AirPig', name: 'Brave-AirPig' }
// Brave-AirPig
// [ '足球', '跑步', '篮球' ]
// Brave-AirPig说了一句话
// Hello
// o { age: 21, nick_name: 'AirPig', name: 'Bun' }

/**
 * 父类可以使用 abstract 开头,叫做抽象类 - 和普通类区别就是他不能创建对象(实例)
 * 抽象类就是专门用来被继承的
 * 抽象类可以定义抽象方法
 */

(() => {
  // 抽象类
  abstract class Animal {
    name: string;
    constructor(name: string) {
      this.name = name;
    }

    // 抽象方法
    // 子类必须对抽象方法进行重写
    abstract sayHello(): void;
  }

  class Dog extends Animal {
    sayHello(): void {
      console.log("汪汪汪");
    }
  }

  class Cat extends Animal {
    sayHello(): void {
      console.log("喵喵喵");
    }
  }

  const dog = new Dog("大黄");
  dog.sayHello();
})();

这就是一些简单的类的操作,而 typescript 给我们又增加了接口这么一个概念

接口

如果我们之前没接触过这个概念的话一定会想:接口?是后端给我们写的接口吗? ????

接口其实和抽象类的用法是一样的,也就是定义一个规则,想要使用该接口,必须实现该接口的规则,接口中的所有的属性是不能有实际值的,他只定义结构,接口中的所有方法无需手动标注即是抽象方法

// 定义接口
interface myInter {
  name: string;
  age: number;

  say(): void;
}

// 实现接口
class Test implements myInter {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  say(): void {
    console.log("测试");
  }
}

console.log(new Test("Brave-AirPig", 66));

// t { name: 'Brave-AirPig', age: 66 }

属性封装

class TestClass {
  // public 修饰属性是公共属性,可以在任何位置被访问以及修改
  // private 修饰属性为私有属性,只能在类的内部进行访问以及修改
  // protected 受保护的属性 只能在当前类以及当前类的子类中访问
  public name: string;
  private age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  /**
   * 属性可以被任意的修改会导致对象中的数据非常的不安全
   * 但是我们设置了私有又没办法操作了,解决方案就是在类的内部通过方法做媒介修改值
   */

  getAge() {
    console.log(this.age);
  }

  setAge(value: number) {
    if (value >= 0) {
      this.age = value;
    }
  }
}

const testPeople = new TestClass("Bun", 66);

console.log(testPeople.name);
testPeople.getAge();

在 ts 中,我们使用方法修改私有属性的话,也可以使用一种更加酷的方法:

class TestClass {
  // public 修饰属性是公共属性,可以在任何位置被访问以及修改
  // private 修饰属性为私有属性,只能在类的内部进行访问以及修改
  // protected 受保护的属性 只能在当前类以及当前类的子类中访问
  public name: string;
  private _age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this._age = age;
  }

  /**
   * 属性可以被任意的修改会导致对象中的数据非常的不安全
   * 但是我们设置了私有又没办法操作了,解决方案就是在类的内部通过方法做媒介修改值
   */

  get age() {
    console.log(this._age);
    return this._age;
  }

  set age(value: number) {
    if (value >= 0) {
      this.age = value;
    }
  }
}

const testPeople = new TestClass("Bun", 66);

console.log(testPeople.name);
testPeople.age = 22;

还有就是我们可以在构造函数中将属性类型声明等等都写在一起,与上面的类的作用是完全一样的

class TestClass {
  constructor(public name: string, private _age: number) {}
  
  get age() {
    console.log(this._age);
    return this._age;
  }

  set age(value: number) {
    if (value >= 0) {
      this.age = value;
    }
  }
}

const testPeople = new TestClass("Bun", 66);

console.log(testPeople.name);
testPeople.age = 22;

泛型

在我们定义函数或者是定义一个类的时候,如果遇到类型不明确就可以使用泛型

/**
 * 泛型
 */

// 泛型、参数、返回值类型都为 T
function func<T>(value: T): T {
  return value;
}

let value1 = func(666); // 不指定泛型,TS 自动推断
let value2 = func<boolean>(true); // 指定泛型

// 指定多个泛型
function fn2<T, G>(v1: T, v2: G): T {
  console.log(v2);
  return v1;
}

fn2<string, boolean>("Hello", true);

// 定义接口,可以让泛型继承自接口
interface Inter {
  length: number;
}

function fn3<T extends Inter>(value: T): number {
  return value.length;
}

console.log(fn3(["Hello", "World"]));

// 也可以使用在类上
class Test02<T> {
  constructor(public name: T) {}
}

new Test02<string>("Bun");

了解更多

TypeScript 中文文档