TypeScript
微软公司, 设计的 TypeScript
TS是什么 ?
出现的目的: 解决 JS 出现的一些问题, TS 是以 JS 为 基础构建的语言,是一个JS的超集
TS 完全支持,兼容JS, 给动态的JS变量类型, 设置为静态的变量类型。 主要是加上了 Type
TS特点
- TypeScript 扩展了 JS, 并添加了类型
- 可以在任何支持JS的平台中执行
- TS不能被JS解析器直接执行, 需要将 TS 编译为 JS文件
增加了类型
变量
ts 的变量
- Ts 的变量声明
// 1. Ts 的变量声明let a:number;// a 的类型设置为了 Number, 在以后的使用过程中 a 的值只能是 number 类型a = 10;a = 33;// a = 'hello'; // 这里直接报错了,不能赋值为 字符串 , 默认情况下,ts 会报错了,依旧会被编译为js文件
- 可以使用 | 来 连接多个类型 (联合类型)
// let c: 'male' | 'female'let c: boolean | string; // c 可以是 boolean 类型, 也可以是 string 类型c = truec = 'true'
- any 类型
// 一个变量设置类型为 any 后,相当于对该变量关闭了Ts的类型检测// let d:any; // any 可以是任以类型 - 显示类型// 同样 直接声明 d,没有确定类型, 它的类型也是 anylet d; // 默认类型为 any - 声明变量,不指定类型, ts 解析器 自动判断类型为 any (隐式类型)d = 12;d = 'abc';d = false;d = [1, 2, 3];// any 的缺点, 会污染其他类型变量// 例如let s: string;s = d; // 不会报错, s 赋值为 d ; d 此时是一个 [] , 而 s 则为 string// s 出现变量被污染 -> any 的缺点
- unknown 表示位置的值
// 4. unknown 表示位置的值let e: unknown;e = 10;e = 'hello';// s = e; // 这里会报错, e 的类型不确定 s -> String, e -> unknown// unknown 实际上就是一个类型 安全的 any// unknown 类型的变量, 不能直接赋值给其他变量 -> 需要类型判断if (typeof e === 'string') {s = e; // 这样就不会报错}
- 类型断言 :直接告诉编译器 xxx 类型就是 xx 类型 实际类型
// 5.// 类型断言 :直接告诉编译器 xxx 类型就是 xx 类型 实际类型s = e as string; // x as 类型// 第二种写法s = <string>e;/*** 语法* 1. xx as <类型>* 2. <类型>xx*/
- void 用来表示没有返回值
// 以函数为例function fn(): void {// 1. 不写return// 2. return; return 为空值// 3. return undefined | null 。 都表示为空// 4. return 123 // 会报错}
- never 表示永远不会返回结果,
// 7. never 表示永远不会返回结果,function fn2(): never {throw new Error('报错了')}
引用类型的变量
- object ```typescript // 1. Ts 的 object
let a:object; // object 表示一个对象
a = {}; a = function () {}; // function 也是一个对象
// a:object 不是很实用, 因为JS中 object 的类型数据实在太多了
<br />使用 `type` 自定义2. _{} 用来指定对象中可以包含哪些属性_```typescript// 2. {} 用来指定对象中可以包含哪些属性// 语法: {属性名: 属性值}// 在属性后面加上 ? 表示属性是可选的let b: {name: string, age?: number};// b = {name: "Yellowsea"};// b = {name: "123", age: 123}; // 报错,必须按照 b 定义的方式,多一个少一个都不行// 在 加上 age? 后b = {name: "Yellowsea", age:123};b = {name: "Yellowsea"}; // 都是可以的// 定义任意多个类型的属性// [propName: string]: any 表示任意类型的属性 propName: 属性名-> string 属性值 :anylet c: {name: string, [propName: string]: any}; // 常用的语法c = {name: "Yellowsea", age:123, sex: '男'} // 这样写多个属性// 2. 设置函数结构的类型声明// 语法: (形参:类型, 形参:类型...)=> 返回值// 设置 d 为一个函数,a,b 都是参数, 返回值是 num 类型let d:(a:number, b:number) => number; // 用来定义函数的结构 -> 常用的语法d = function (n1, n2) { // n1, n2 对应 a,b 。 多一个少一个都不行return n1 + n2}
- array 数组
```typescript
// 3. array 数组
// Ts定义 数组的语法:
/**
- 类型[]
- Array<类型> */
// string[] 表示字符串数组 let e:string[];
e = [‘a’,’b’, ‘c’]; // good // e = [‘1’, ‘2’, 3]; // bad
// number[] 表示数值数组 let f:number[];
// 或者
let g: Array
4. _元组,元组就是固定长度的数组_```typescript/*** 4. 元组,元组就是固定长度的数组* 定义元组: let h: [string, string];* - 语法: [类型, 类型, 类型]*/// 定义元组,let h: [string, string]; // 固定两个长度的 string 类型h = ["hello", "abc"]; // good// h = [1, "1"] // bad
- enum 枚举
```typescript
/**
- enum 枚举
- Ts 语法特有的 */
// 普通定义数据时 设置性别为 0 | 1 // let i: {name: string, gender: 0 | 1};
// i = { // name: ‘Yellowsea’, // gender: 0 // }
// 但是这样定义会出现问题,当用户使用时,不知道 0 | 1 是什么、
// 使用枚举 enum Gender { // 定义枚举 // 不需要知道枚举的内容是什么 Male, Female }
// 使用枚举 let i: {name: string, gender: Gender}; // gender 使用 枚举, Gender
i = { name: ‘Yellowsea’, // gender: Gender.Male gender: Gender.Female, // 这是好的定义 }
console.log(i.gender == Gender.Male);
6. _补充内容_```typescript// 6. 补充内容// | 或// let j: string | number; // j 的类型是 string 或 number 类型// j = "hello" | j = 123// & 与// let j: {name: string} | {age: number}let j: {name: string} & {age: number}j = {name: 'yellowsea', age: 123};
tsconfig配置选项
使用 tsc --init 进行 初始化 , 会自动创建 tsconfig.json 文件
include
include 编译指定目录下的需要编译的文件
{/**include配置ts编译器,编译指定目录下的需要编译的文件写在 tsconfig.json 的最外侧路径: ** 表示任意目录* 表示任意文件*/// 编译指定目录下的需要编译的文件"include": ["./src/**/*"],}
exclude
_exclude_ : 需要排除编译的文件
{/**exclude : 需要排除编译的文件// 它是可选的, 具有默认值: ["node_modules", "bower_components", "jspm_packages"]*/"exclude": [// 不让 hello 文件夹下的文件被编译"./src/hello/*"],}
extends
_extends_ 继承 配置
{/*extends 继承 配置。- 比如你还有另一个 xxx.json 的配置文件,可以通过 extends 引入进来- 相当于 合并两个文件的配置- 这里不演示- 语法: "extends": "./configs/base"*/"extends": "./configs/base.json",}
file
_file_ 表示需要编译的单个文件, 需要列举出来
{/*flies : 文件。 跟include 类似,- 表示需要编译的单个文件, 需要列举出来语法:files: ["./fileName.ts",...]// 在 base.json 中 配置 files*/}
compilerOptions
“_compilerOptions_“ 编译器的选项, 在 tsconfig.json 中最重要的配置
学习配置ts , 也就是 学习配置 compilerOptions , 学会它的 子选项的配置
这里的配置项基本上都有自己的默认值,如果需要配置某个配置, 设置一个错误的值,编译后出错,可以看到所有的选项
1. target
// ts 编译器的选项"compilerOptions": {//1. target 用来指定 ts 文件被编译为 ES 几 的版本// 默认TS会转为 ES3 版本, 兼容行好// target: 的属性值 :// 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'."target": "es6",}
2. module
"compilerOptions": {//2. module 指定要使用的模块化的规范// module的属性值: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'"module": "commonjs",}
3. lib
"compilerOptions": {//3. lib 用来指定项目中要用到的库 - 浏览器运行环境// 一般不去动它,也不用去配置,如果写了, 什么都不配置,表示没有使用任何库, 然后写TS代码时候,没有任何补全// 它的属性值很多,在写 lib: xx 让它报错,然后可以查看它的属性值// "lib": ["DOM", "ES2015"]}
4. outDir
"compilerOptions": {//4. outDir 用来指定编译后文件所在的目录// 表示 编译TS文件后 生成 JS文件存放的目录, 将源码 和 生成的目录分开"outDir": "./dist",}
5. outFile
"compilerOptions": {// 5. outFile 将代码合并为一个文件// 设置 outFile 后,所有的全局作用域中的代码 会合并到同一个文件中// "outFile": "./dist/app.js", // 使用时 module 需要改为 system// 用的不多, 一把结合打包工具使用}
6. allowJs
"compilerOptions": {// 6. allowJs 是否 对JS文件进行编译, 默认 false// 改为true 后,同样编译 src 目录下的 js 文件// "allowJs": false,"allowJs": true, // 一般用在项目中,不得不使用的第三方库, js 文件的编译}
7. checkJs
"compilerOptions": {// 7. checkJs 是否检查js代码是否符合语法规范, 默认值为 false// "checkJs": false, // 不检查js的语法"checkJs": true, // 和 allowJs 具有冲突,一般使用它们之间的其中一个}
8. removeComments
"compilerOptions": {// 8. removeComments : 在编译ts中, 是否移除注释 , 默认为false"removeComments": true,}
9. noEmit
"compilerOptions": {// 9. noEmit 不生成编译后的文件, 默认为false// "noEmit": false// "noEmit": true, // 不常用}
10. noEmitOnError
"compilerOptions": {// 10. noEmitOnError : 当有错误时候 不生成 编译后的文件, 默认为 false"noEmitOnError": true,}
11. strict
TS 的语法检查 选项
"compilerOptions": {// 接下来就是 TS 的语法检查 选项// strict : Ts 语法检查的总开关, 默认为 false,"strict": true, // 项目中都会打开,这样代码出错的比较少}
alwaysStrict
"compilerOptions": {// 接下来就是 TS 的语法检查 选项// strict : Ts 语法检查的总开关, 默认为 false,"strict": true, // 项目中都会打开,这样代码出错的比较少// 11. alwaysStrict : 用来设置编译后的文件是否使用 严格模式, 默认为 false"alwaysStrict": true, // 编译生成的 JS文件, 会加上 严格模式 : "use strict";}
noImplicitAny
"compilerOptions": {// 接下来就是 TS 的语法检查 选项// strict : Ts 语法检查的总开关, 默认为 false,"strict": true, // 项目中都会打开,这样代码出错的比较少// 12. noImplicitAny : 是否允许隐式的 any 类型 , 默认是false"noImplicitAny": true,}
noImplicitThis
"compilerOptions": {// 接下来就是 TS 的语法检查 选项// strict : Ts 语法检查的总开关, 默认为 false,"strict": true, // 项目中都会打开,这样代码出错的比较少// 13. noImplicitThis : 是否允许 不明确类型的 this , 默认值为 false;"noImplicitThis": true,}
strictNullChecks
"compilerOptions": {// 接下来就是 TS 的语法检查 选项// strict : Ts 语法检查的总开关, 默认为 false,"strict": true, // 项目中都会打开,这样代码出错的比较少// 14. strictNullChecks :严格检查空值"strictNullChecks": true,}
webpack + TS
项目初始化
mkdir webpack-ts && cd webpack-tsnpm init -ytsc --initmkdir src && cd src && touch index.tstouch webpack.config.js
// 安装的依赖npm install -D \ts-loader \typescript \webpack \webpack-cli \webpack-dev-server \html-webpack-plugin \core-js \@babel/core \@babel/preset-env \babel-loader \
源代码
src/index.ts
// 省略了 m 的扩展名import { name } from "./m"console.log("Hello webpack+Ts");let div = document.createElement('div');// const box = document.querySelector('#box');if (div !== null) {div.innerHTML = `<h1>Hello Webpack + Ts </h1>`}document.body.append(div);// 写好的TS代码function sum (a: number, b: number): number {return a + b;}console.log(sum(1,2));console.log(sum(123, 456));console.log(sum(123, 777));console.log(name);// m.tsexport const name = "yellowsea";let num = 123;num = 10;console.log(num);
src/index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>webpack + Ts</title></head><body></body></html>
配置项
package.json
{"scripts": {"test": "echo \"Error: no test specified\" && exit 1","build": "webpack","start": "webpack serve --open"},}
tsconfig.json
{// ts 编译器的配置"compilerOptions": {// 也可以使用 commonjs"module": "ES6","target": "ES6","strict": true,"sourceMap": true, // 编译后的文件出错时, 回溯源代码的所在的位置的映射}}
wbepack.config.js
const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {// mode: 'development',mode: 'production',entry: {index: './src/index.ts',},output: {filename: 'bundle.js',path: path.resolve(__dirname, 'dist'),clean: true},//module: {rules: [// 规则{test: /\.ts$/,use: [// 配置babel{loader: 'babel-loader',// 配置babeloptions: {// 设置预定的环境presets: [[// 指定的环境"@babel/preset-env",// 配置信息{// 要兼容的浏览器targets: {"chrome": "58","ie": "11"},// 指定corejs 的版本"corejs": "3",// 使用 corejs 的方式, "usage" 表示按需加载"useBuiltIns": "usage"}]]}},'ts-loader'],exclude: /node_modules/}]},plugins: [new HtmlWebpackPlugin({title: 'webpack + ts',template: './src/index.html'})],resolve: {// 省略引入的扩展名extensions: ['.js', '.jsx', '.ts']}}/*** {* loader: 'babel-loader',* presets: [* "@babel/preset-env"* ]* }*/
TS 类
TS 的类和JS类基本一样,都是使用 class 关键字
类中包含主要两部分
- 属性
- 方法
class Person {// 定义属性 - 在 class 内的 叫做实例属性 - 需要通过 实例去访问name:string = 'Yellowsea';// 加上 readonly 后 只能读, 不能修改readonly age:number = 123;// 在属性前 使用 static 关键字 可以定义 类属性, 叫做静态属性// 静态属性: 定义类时, 不用创建实例,通过类名,能够访问到的属性static test:string = '这是static test'// 定义方法 --- 实例方法sayHello() {console.log("Hello")}// 静态方法static sayHello2() {console.log("Hello2")}}const p = new Person();console.log(p);console.log(p.age);console.log(p.name);// 通过实例修改 实例属性p.name = 'Hidie'console.log(p.name);// p.age = 123123; // readonly ,修改不了// console.log(Person.age); // 访问不到 ageconsole.log(Person.test); // 能够访问到testPerson.test = "testtest"; // 可以修改, 静态属性加上 readonly 后也修改不了console.log(Person.test);// 通过实例调用 方法p.sayHello();// 加上 static 方法名 , 静态方法Person.sayHello2();
类的构造函数
class Dog {// TS 需要在类中 提前定义 name age 并赋予类型name: string;age: number;// 构造函数 constructor// 构造函数 是在new XXX 的时候, 传递参数,在构造函数中能够获取得到 参数constructor(name:string, age:number) {// 在 new XXX 的 时候执行 , 此时的 this 就表示当前的的 实例// console.log("constructor执行了", this)this.name = name;this.age = age;// console.log(this)}bark() {// 这里的 this 表示 当前调用方法的对象console.log("bark", this)}}const dog = new Dog("小黑", 123);const dog2 = new Dog("小白", 123);dog.bark();dog2.bark();
继承
// 将所有的 变量 写在 这个作用域里// class Dog {// name: string;// age: number;// constructor(name:string, age:number) {// this.name = name;// this.age = age;// }// sayHello () {// console.log('wangwanga')// }// }// class Cat {// name: string;// age: number;// constructor(name:string, age:number) {// this.name = name;// this.age = age;// }// sayHello () {// console.log('wangwanga')// }// }
// 因为在学习ts过程中, ts 会检查当前目录的所有变量, 当变量名相同 就会报错// 使用 立即执行函数 解决(function () {class Animal {name: string;age: number;constructor(name:string, age:number) {this.name = name;this.age = age;}sayHello (call:string) {console.log(call)}}// 使用 extend 关键字 继承 父类的 方法 和 属性class Dog extends Animal {// Dog 类 本身自己的方法run () {console.log(this.name, "在跑````")}}class Cat extends Animal {// 修改父类的 sayHello 方法sayHello() {console.log(this.name, "miaomiaoamia asdsadasd")}}const dog = new Dog("旺财", 123);dog.sayHello("wangwangawang");dog.run();const cat = new Cat('咪咪', 123123);cat.sayHello();//})()
super
// super 关键字(function () {class Animal {name: string;constructor(name:string) {this.name = name;}sayHello () {console.log('sayHello');}}class Dog extends Animal {// 2. 在子类中使用 构造函数age: number;constructor(name:string, age:number) {// 在最前面 调用父类的 constructor 方法,// 直接 调用 super() - 必须手动调用super(name) // 这里就是在调用 父类的构造函数this.age = age; // 报错// 因为,继承时,相同的方法会发生重写, 子类写了 constructor 构造函数,相当于重写了 父类的构造函数// 解决, 在使用子类的 构造函数时, 必须先进行 对父类的构造函数的调用 , 执行 super()}// 1.sayHello () { // 表示重写// 然后使用 super 关键字super.sayHello();// 这里的super 表示当前的父类, 能够拿到父类的的属性|方法// super , 一般用在 子类 继承 父类时, 需要使用 父类的 方法 或 属性时, 进行调用// 例如 调用 查看父类的 name// console.log(super.name); // undefined// console.log(super); // super 后面必须要跟 父类的 某一个属性 或 方法}}const dog = new Dog("旺财", 123);dog.sayHello()})()
抽象类
(function () {/*** 以 abstract 开头的类是 抽象类* - 抽象类和其他类区别不大, 只是不能 用来创建兑现实例化* - 抽象类就是专门用来被继承的类*/abstract class Animal {name: string;constructor(name:string) {this.name = name;}// 定义一个抽象方法,// 抽象方法使用 abstract 开头, 没有方法体// 抽象方法只能定义在 抽象类中, 子类必须对抽象方法进行重写abstract sayHello():void;// sayHello () {// console.log(this.name);// console.log('sayHello');// }}class Dog extends Animal {//必须要对 sayHello方法进行重写sayHello() {console.log(this.name);}}class Cat extends Animal {sayHello() {console.log(this.name);}}const dog = new Dog("🐕");dog.sayHello()const cat = new Cat("🐱")cat.sayHello()// 这里直接 实例化 Animal 这里大的总类,// const an = new Animal("🐱") // 使用 abstract class Animal 抽象类后 这里不能够 实例化了// an.sayHello()// 以上类方法中的 Animal 类, 如果 Animal 是一个 大型的 类, 并且只能拿来继承// 所以 可以把Animal 变为一个 抽象类})()
属性的封装
/*** 属性的封装*/(function() {class Person {// Ts 独有的 对属性的 修饰方式/*** 1. public 修饰的属性可以在任意位置 访问 包括子类 (修改) 默认值,** 2. private 私有属性, 私有属性只能在类内部进行访问,(修改)* - 如果徐需要修改,通过在类中添加方法使得私有属性可以被外部访问** 3. protected 受包含的属性,只能在当前类 和 当前类的子类 能访问,其他情况下不能访问*/// 默认值 public _name 可以读, 也可以修改// _name: string;// _age: number;private _name: string;private _age: number;constructor(name: string, age: number) {this._name = name;this._age = age;}/*** getter 方法用来读取属性* setter 方法用来设置属性* 这两个方法被称为属性的存储器** 一把JS中对 属性的处理基本上是这样处理*// 读取属性值getName() {return this._name;}// 修改属性值setName(value: string) {this._name = value;}// age 也一样getAge () {return this._age}setAge (value: number) {// 进行判断if (value >= 0) {this._age = value}}*//*** 在 TS中 提供了 一种更灵活的方式对 类中的属性 监控**// get 属性名 () {} ->// 然后实例中 使用 p.属性名 访问, 相当于 调用了TS定义的 get 属性名这个方法*/// 读取属性方法get name() {console.log("get name () 方法被调用了")return this._name// 然后在 实例中 使用 p.name 调用 get name () 方法}// 修改属性方法set name (value:string) {console.log("set name () 方法执行")this._name = value;}// 同理, age也一样get age() {return this._age}set age(value:number) {if (value >= 0) {this._age = value}}}/*** 现在属性是在对象中设置,属性可以任意的被修改* - 属性可以任被修改将会导致对象中的数据 变得 非常不安全*/const p = new Person("Yellowsea", 123);console.log(p)// 添加 private 后 实例对象 就不能通过 .属性名 修改属性值了// p._name = 'Hidie'// p._age = 456// 实例对象通过 类中的 方法 修改属性值// console.log(p.getName()) // 通过get 方法获取属性值// p.setName('Hidie') // 通过 set 方法修改 属性值// console.log(p.getAge())// // p.setAge(-123) //经过判断, 修改不了// p.setAge(456)// console.log(p)// 实例使用 TS 的 get set 方法访问属性console.log(p.name); // get name () 方法被调用了 ——> Yellowseap.name = 'Hidie'; // set name () 方法执行// 同理 ageconsole.log(p.age);p.age = 456;console.log(p);// 2. 这里讲 protected// protected 受包含的属性,只能在当前类 和 当前类的子类 能访问,其他情况下不能访问class A {// num 属性,是一个 protected 限制的protected num: number;constructor(num: number) {this.num = num;}}class B extends A {// B 继承了Ashow() {// 子类B 能够访问到 A 中的 numconsole.log(this.num);}}const b = new B(123);b.show() // 123// 3. 属性的封装简洁写法class C {// 直接在构造函数中 确定的 属性的类型,不用再写this.xxx = xxxconstructor(public num: number, public age: number) {}}const c = new C(123,456);console.log(c)})()/*** Ts 中提供的 getter setter 方法* 使用场景:当对某一个属性值 有比较高的 严格需求时*/
接口
(function () {// 使用接口来描述对象类型/*** 接口: 用来定义一个类的结构 , 用来定义一个类中 应该包含哪些 属性 和 方法* - 同时接口也可以当成类型声明去使用* - 接口可以重复声明*/// 1. 接口用于 类型声明, - 使用在对象中, 跟 type Mytype = {} 类似// 定义接口interface myObject {name: string;age: number;}// 接口可以重复声明 , 相当于合并 两个接口interface myObject {gender: string;}// 使用接口const obj: myObject = {name: "yellowsea",age: 1,gender: "男"}console.log(obj)/*** 2. 接口可以在定义类的时候去限制类的结构* - 接口中的所有属性都不能有实际的值* - 接口只定义对象的结构,而不考虑实际的值*/interface myInter {name: string;sayHello(): void; // 返回值为 空// 接口中的所有属性都不能有实际的值// 接口只定义对象的结构,而不考虑实际的值// 接口里的 方法 就是 抽象方法}// 定义类时, 可以使用类去实现一个接口 , 必须使用 implements 指定的 接口// 这个类就是 满足接口的要求class MyClass implements myInter {name: string;constructor (name: string) {this.name = name;}sayHello(){console.log("Hello")}}/*** 最后讲讲 接口的作用* - 接口实际就是定义了一个规范, 当类满足了规范,才能在特定的场景使用* - 实际上是对类的限制*/})()
泛型
/**** 08 泛型*/// 问题:// function fn (a: number):number {// return a;// }/*** fn 函数,确定了 a 的类型时,它的返回值同样也确定了** 当 a 的类型不确定时, 有应该怎么 保证 a 的类型 和 函数的返回值呢** - 使用 any , 但是使用了 any, 在 TS 中就相当于关闭了 变量的类型检查。** - 使用泛型。 当函数中的类型不确定时, 使用泛型*/// 使用泛型function fn<T>(a: T):T { // 这里的 T 就是泛型,return a;}// 调用let result = fn(10) // 泛型 T, 自动检查 参数 10 ,然后确定 T 的类型 -> number// 使用了 TS 中的 自动判断类型let result2 = fn<string>('hello') // 手动指定 泛型 T 的类型为 string// 泛型的好处, 就是确定了 参数类型 的明确// 在调用时, 不用担心类型的不明确// 2. 泛型可以指定 多个function fn2<K, T>(a: T, b: K):T {console.log(b)return a}// 使用时,最好加上类型, 这样更好的避免出错fn2<string, number>(123,'Hello')// 3. 指定接口的泛型interface Inter {length: number}// T extends Inter 定义的泛型 T 指定 Inter 接口// T extends Inter 泛型T 必须实现 Inter 这个类function fn3<T extends Inter>(a: T):number {return a.length}fn3('Hello') // str 中有 length 属性// fn3(123) // error ,// fn3({name:'yellowsea'}) // errorfn3({length: 2}) // 指定length的属性// 4. 在类中使用 泛型 Tclass MyClass<T> {// 类中使用 泛型 Tname:T;constructor(name:T) {this.name = name;}}const myc = new MyClass(123)const myc1 = new MyClass('123')/*** 总结:** 泛型就是 在变量不明确时,使用一个变量,用这个变量来表示 泛型**/
