TypeScript 是什么
ts 是 js 的超集
❓ ts 相对于 js 的优势:
- 类型化思维方式,开发更加严谨,提前发现错误,减少改 bug 时间
- 类型系统提高了代码可读性,并使维护和重构代码更加容易
- 补充了接口,枚举等开发大型应用时 js 缺失的功能
ts 的环境准备
ts 的相关命令行
npm install -g typescript
tsc -v
创建并执行一个 ts 文件
创建一个 .ts 的文件
var message:string = 'Hello World'console.log(message)
执行以下命令,将 TypeScript 转为 JavaScript
tsc test.ts
使用 node 命令执行转换后的 .js 文件
node test.js
简化执行 ts 步骤
简化方式一:使用 ts-node 包,
“直接”在 node.js 或浏览器中执行 ts 代码(会在内部偷偷的调用)
安装命令
npm i -g ts-node
使用方式
ts-node hello.ts
💞 ts-node 相当于 tsc 和 node 的整合

❓问题: 每次修改 ts 文件后,都要重新运行 tsc 命令将 ts 转换为 js
🈶解决方案:使用 tsc 命令的监视模式 tsc —watch test.ts
简化方式二:配置 vscode 编辑器,ts 转换 js
- 使用终端 cd 项目文件夹下,tsc —init 生成 tsconfig.json 文件
- 修改 tsconfig,json 的输出格式未 ./js

- 终端 → 运行任务 → typescript → tsc:监视tsconfig.json

如果 .ts 文件中没有ts语法,只有js代码,可以被直接在页面引入调用的。
TypeScript 语法
类型注解
let age:number;// 数组的类型注解let names:string[] = []// 函数的类型注解function fn():number {}
❓问题: let img = documnet.querySelector(‘image’); img 获取到的类型为Element类型,只能调用公共的属性,比如id,获取不到src属性等; 因此就要用到了类型断言
类型断言:手动指定更加具体(精确)的类型
let img = document.querySelector('img') as HTMLImageElement;img.src = './3.png'
💡当你不知道什么类型的时候,可以利用 console.dir() 查看类型
ts 中操作 dom 样式
在 ts 中,如果样式是两个单词,则使用驼峰法:
let p = document.querySelector('p') as HTMLParagraphElement;p.style.fontSize = '30px';
classList 属性(类样式)
let p = document.querySelector('p') as HTMLParagraphElement;// 添加类p.classList.add('c', 'b')// 移除类p.classList.remove('b')// 判断类名是否存在let has = p.classList.contains('b')
枚举类型
枚举,可以防止出错,比如说:防止o写成0
语法:enum 枚举名称 { 成员1, 成员2 } 约定枚举名称、成员名称以大写字母开头
enum Gender {Female,Male}let userGender:GenderuserGender = Gender.FemaleuserGender = Gender.Male
💡注意:枚举的成员是只读的,因此,只能访问不能赋值
数字枚举:具有自增长行为enum Gender {Female,Male}// Female = 0 Male = 1enum Gender {Female = 100,Male}// Female = 100 Male = 101
🔈数字枚举:具有自增长行为
字符串枚举:没有自增行为,需要为每一个成员赋值enum Gender {Female = '女',Male = '男'}
符号注意事项
- 除加号外,其他算术运算符必须与数字类型一起使用
- 在字符串类型的前面加上一个 ‘+’号,可以转换为 number 类型:
let n:number = +’12’
console.log(3 === 4) // 会报错/* 正确用法 */let num1:number = 3let num2:number = 4console.log(num1 === num2) // false
ts 转换 js
class Site {name():void {console.log('Hello World')}}var obj = new Site()obj.name()
var site = /** @class */ (function() {function Site() {}Site.prototype.name = function() {console.log('Runoob')}return Site}())var obj = new Site()obj.name()
ts 基础类型
- number
- string
- boolean
- null 表示对象确实,能找到值
- undefined 表示找不到值
- void
let name:string = 'Runoob'let years:number = 5let words:string = `您好,今年是${ name }发布${ years+1 }周年`let flag:boolean = true
类型推论
// 直接赋值,ts 是可以进行类型推论的 (age:number)let age = 18
// 再次赋值,ts 是不会进行类型推论的 (age:any)let ageage = 18
定义数组的两种方式
let arr1:number[];let arr2:Array<number>let arr3:any[] = ['123', '456', true]
元组类型(tuple):属于数组的一种
换种说法,就是在数组中,可以指定具体的类型let arr:[string, number, boolean] = ['ts', 3.08, true]
any 任意类型
任意类型的用处:var num:any = 123num = 'str'console.log(num)
null 和 undefined 是其他类型(never)的子类型
定义没有赋值的情况
var num:number;console.log('num', num) // 语法报错,可采用以下写法var num:number | undefined;console.log((num);
定义 null 的情况
var num:number | null;num = null
一个元素可能是 number 类型,也可能是 null 类型,也可能是 undefined 类型
var num:number | null | undefined;num = 1234;
void 类型:ts中的void表示没有任何类型,一般用于定义方法的时候方法没有返回值
function run():void {console.log('run')}run();
never类型:是其它类型(包括null 和 undefined)的子类型,代表不会出现的值
这意味着声明 never 的变量只能被 never 类型所赋值
var a:never;a = (() => {throw new Error('错误')})
ts 中函数的定义方法
函数声明法
function run():string {return 'run';}
function run(name:string, age:number):string {return `${name} --- ${age}`}run('zhangsan', 20)
函数匿名法
var fun2 = function():number {return 123;}
var fun2 = function(name:string, age:number):string {return `${name} --- ${age}`}fun2('zhangsan', 40)
方法可选参数:?:
es5里面方法实参和形参可以不一样,但是ts中必须一样,如果不一样就需要配置可选参数
💡注意:可选参数必须配置到参数的最后面
function getInfo(name:string, age?:number):string {if(age) {return `${name} --- ${age}`} else {return `${name} --- 年龄保密`}}getInfo('zhangsan')
上述案例,因为少传一个参数,必须使用可选参数 ?:,否则 ts 语法报错
默认参数(可选参数)
es5 中不可以设置默认参数,es6 和 ts 中都可以设置默认参数
function getInfo(name:string, age:number=20) {return `${name}---${age}`}getInfo('zhangsan')
剩余参数
function sum(...result:number[]):number {var sum =0;for(var i=0; i<result.length; i++) {sum+=result[i]}return sum;}sum(1, 2, 3)
function sum(a:number, ...result:number[]):number {var sum = a;for(var i=0; i<result.length; i++) {sum+=result[i]}return sum;}
函数重载
java 中方法的重载,重载指的是两个或者两个以上同名函数,但他们的参数不一样,会出现函数重载的情况。
ts 中的重载,通过为同一个函数提供多个函数类型定义来试下多种功能的目的。
ts 为了兼容 es5以及es6 重载的写法和 java中有区别
function getInfo(name:string):stringfunction getInfo(age:number):stringfunction getInfo(str:any):any {if(typeof str === 'str') {return `name: ${str}`} else {return `age: ${str}`}}getInfo('zhangsan') √ // 正确写法getInfo(20) √ // 正确写法getInfo(true) ❌ // 错误写法
类和继承 extends super
class Person {name:string;constructor(n:string) {this.name = n}run():void {alert(this.name)}}class Web extends Person {constructor(n:string) {// 初始化父类的构造函数super(n)}}var w = new Web('lisi')w.run()
类里面的修饰符
| public | 公有 | 在类里面、子类、类外面都可以访问 |
|---|---|---|
| protected | 保护类型 | 在类里面、子类里面可以访问, 在类外部没法访问 |
| privated | 私有 | 在类里面可以访问,子类、类外部都没法访问 |
属性如果没有设置,默认公有(public)
class Person {protected name:string;private age:number;constructor(name:string) {this.name = name}// 类内部可以使用work():void {console.log(`${this.name}在工作`)}}class Web extends Person {constructor(name:string, age:number) {// 初始化父类的构造函数super(name, age)}// 子类内部可以使用run():void {console.log(`${this.name}在运动`) √console.log(`年龄:${this.age}`) √}}var w = new Web('lisi');w.run()w.work()var p = new Person()// 外部不可以访问,报错console.log('name', p.name) ❌
ts 中的静态方法
class Person {name:string;constructor(name:string) {this.name = name}// 实例方法run() {console.log(`奔跑的${this.name}`)}// 静态方法static print() {}}// 通过类名调用静态方法和属性Person.print() √// 不能通过实例对象调用:const person = new Person('小甜甜')person.print() ❌
多态
父类定义一个方法不去实现,让继承它的子类去实现。每一个子类有不同的表现
多态属于继承
ts中的抽象类,它是提供其他类继承的基类,不能直接被实例化。
用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须派生在派生类中实现。
抽象方法只能放在抽象类里面。
抽象类和抽象方法用来定义标准,例如:Animal这个类要求它的子类必须包含eat方法
// 抽象类abstract class Animal {name:string;constructor(name:string) {this.name = name}abstract eat():any}class Dog extends Animal {constructor(name:string) {this.name = name}// 必须定义 eat 方法eat() {console.log(`${this.name}在吃粮食`)}}var d = new Dog('小小')d.eat()
接口:行为和动作的规范,对批量方法进行约束
可利用接口代替繁琐的类型注解
interface IPerson {firstname:string;lastname:string;sayHi()=>string}var customer:IPerson {firstname: "Tom",lastname: "Hanks",sayHi:():string => {return 'Hi Here'}}console.log('Customer 对象')console.log(customer.firstname)console.log(customer.lastname)console.log(customer.sayHi())/*输出结果:Customer 对象:TomHanksHi Here*/
属性接口
interface fullName {firstName:string; // 注意:以分号结束secondName:string;}function printName(name:fullName) {// 必须传入对象 firstName secondNameconsole.log(name.firstName+'---'+name.secondName)}var obj = {age:20,firstName: '张',secondName: '三'}// 形式1在外部定义对象,可多传参数// 除了必传firstName,secondName,还可以传ageprintName(obj)// 形式2,严格按照对应方式// 只能传firstName,secondNameprintName({firstName:'zhang',secondName: 'san'})
接口:可选属性
interface fullName {firstName:string;// 可选属性secondName?:string;}function printName(name:fullName) {console.log('name', name.firstName)}var obj = {firstName" 'zhang'}printName(obj)
接口:函数类型接口
对方法传入的参数及返回值进行约束(批量约束)
加密的函数类型接口
interface screCN {(key:string, value:string):string}var md5:screctN = function(key:string, value:string):string {return key+value}console.log(md5('name', 'zhangsan'))
可索引接口:数组、对象的约束(不常用)
// 可索引接口:对数组的约束interface userArr {[index:number]:string}var arr:userArr = ['zhang', 'san']console.log(arr[0])
// 可索引接口:对对象的约束interface userObj { // 很少用[index:string]:string}var arr:userObj = {name: '张三'}
类类型接口
对类的约束和抽象类有点相似
关键词:implements
interface Animal {name:string;eat():void;}class Dog implements Animal {name:string;constructor(name:string) {this.name = name;}eat() {console.log(`${this.name}吃两素`)}}var d = new Dog('小黑')d.eat()
接口扩展
接口继承接口
interface Animal {eat():void;}interface Person extends Animal {work():void;}class Web implements Person {name:string;constructor(name:string) {this.name = name}work() {console.log(`${this.name}在工作`)}eat() {console.log(`${this.name}在吃饭`)}}
继承类,实现接口继承
interface Animal {eat():void;}interface Person extends Animal {work():void;}class Programmer {name:string;constructor(name:string) {this.name = name;}code() {console.log(`${this.name}在写代码`);}}class Web extends Programmer implements Person {constructor(name:string) {super(name)}eat() {console.log(`${this.name}在吃饭`)}work() {console.log(`${this.name}在工作`)}}var w = new Web('小李')w.code()
类和接口
类可以实现接口,使用关键字 implements,并将 interest 字段作为类的属性使用
interface IAron {interest:number}class Aro implements IAron {interest:number;rebate:numner;constructor(interest:number, rebate:number) {this.interest = interest;this.rebate = rebate}}var obj = new Aro(10, 1);console.log("利润:", obj.interest, "提成:", obj.rebate)
泛型
可以支持不特定的数据类型
要求:传入的参数和返回的参数一致
T表示泛型(A也可以表示),具体什么类型是调用这个方法的时候决定的
function getData<T>(value:T):T {return value;}getData<number>(123); ✔getData<string>('123'); ✔getData<string>(123); ❌
泛型类
// 无论传入数字还是字符串,判断大小,返回最小的元素class MinClass<T> {list:T[] = []add(value:T):void {this.list.push(value)}min():T {var minNum = this.list[0]for(var i=0; i<this.list.length; i++) {if(minNum > this.list[i]) {minNum = this.list[i]}}return minNum}}// 实例化类,并且制定了类的T代表的类型是Numbervar m1 = new MinClass<number>();m1.add(11);m1.add(12);m1.min();// 实例化类,并且制定了类的T代表的类型是stringvar m2 = new MinClass<string>();m2.add('a');m2.add('b');m2.min();
泛型接口
interface ConfigFn {// 任意类型的函数,返回任意类型的值<T>(value:T):T}var getData:ConfigFn = function<T>(value:T) {return value;}getData<string>('123'); ✔getData<string>(123); ❌
把类作为参数的泛型类
// 定义数据库泛型类class MysalDB<T> {add(info:T):boolean {console.log(info)return true;}}/**********************方式1************************/class User {name:string | undefined;password:string | undefined;}var u - new User();u.name = '张三';u.password = '123456';// 将User类作为数据库类的参数var db = new MysqlDB<User>();db.add(u);/***********************方式2**********************/class Articles {title:string | undefined;desc:string | undefined;sttus:string | undefined;// 构造函数另一种方式constructor(params:{title:string | undefined;desc:string | undefined;status?:string | undefined}) {this.title = params.title;this.desc = params.desc;this.status = params.status}}// 实例化文章类var a = new Articles({title: '分类',desc: '111'})var db = new MysqlDB<Articles>();db.add(a)
综合案例:模块 → 封装DB类库
封装数据库DB类库,到处数据库类
文件目录:module/db.tsinterface DBI<T> {add(info:T):boolean;}export class MysalDB<T> implements DBI<T> {constructor() {console.log('数据库已连接!')}add(info:T):boolean {console.log(info);return true;}}
封装User模块类,对数据库模块类进行映射,到处User类
文件目录:model/user.tsimport { MysqlDB } from '../modules/db';class UserClass {username:string | undefined;password:string | undefined;}var UserModel = new MysqlDBI<UserClass>();export {UserClass,UserModel}
index.ts 页面使用User模块类
import { UserClass, UserModel } from '../model/user'var u = new UserClass()u.username = 'zhangsan'u.password = '123456'UserModel.add(u)
命名空间和模块的区别
命名空间:内部模块,主要用于组织代码,避免命名冲突。(namespace)
模块:ts 的外部模块的构建,侧重于代码的复用,一个模块里可能有多个命名空间。
namespace A {interface Animal {name:string;eat():void;}export class Dog implements Animal {name:string;constructor(name:string) {this.name = name;}eat() {console.log(`${this.name}在吃`)}}}namespace B {interface Animal {name:string;eat():void;}export class Dog implements Animal {name:string;constructor(name:string) {this.name = name;}eat() {console.log(`${this.name}在吃饭呢`)}}}var d = new A.Dog('小黑');d.eat(); // 小黑在吃
命名空间也可以单独封装,到处使用
// 封装命名空间export namespace A {interface Animal {name:string;eat():void;}export class Dog implements Animal {name:string;constructor(name:string) {this.name = name;}eat() {console.log(`ths.name`在吃狗粮)}}}
// 使用封装的命名空间import { A } from './Animal'var d = new A.Dog('二哈')d.eat()
装饰器
装饰器是一种特殊类型的声明,它能被附加到类声明、方法、属性或参数上,可以修改类的行为。
通俗的讲:装饰器是一个方法,可以注入到类、方法,属性参数上来扩展类、属性、方法、参数的功能
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
💡装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)
装饰器是过去几年中最大的成就之一,已是ES7的标准之一
类装饰器
类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于构造函数,可以用来监视,修改或替换类定义。传入一个参数
// 普通装饰器:无法传参function logClass(params:any) {console.log(params)// 扩展属性params.prototype.baseUrl = '******';// 扩展方法params.prototype.eat = function() {console.log('一直在吃')}}@logClass // 调用装饰器class HttpClient {constructor() {}getData() {}}var http:any = new HttpClient();console.log(http.baseUrl) // ******http.eat() // 一直在吃
// 装饰器工厂(传参)function logClass(params:string) {return function(target:any) {console.log('参数', params) // helloconsole.log('类', target) // HttpClienttarget.prototype.apiUrl = '1234567'}}@logClass('hello')class HttpClient {constructor() {}getData() {}}var http:any = new HttpClient()console.log(http.apiUrl) // 1234567
装饰器重载类构造函数和方法
function logClass(target:any) {return class extends target {apiUrl:any = '我是修改后的apiUrl'getData() {console.log(this.apiUrl)}}}@logClassclass HttpClient {apiUrl:string | undefined;constructor() {this.apiUrl = '我是构造函数里的apiUrl'}getData() {console.log(this.apiUrl)}}
属性装饰器
属性装饰器表达式会在运行时当做函数被调用,传入下列2个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 成员的名字 ```typescript function logProp(params:any) { return function(target:any, attr:any) { target[attr] = params; } }
class HttpClient { // 调用属性装饰器 @logProp(‘www.baidu.com’) url:string | undefined; constructor() {} // 经过属性装饰器,现在url有默认值 getData() { console.log(this.url) } }
var http = new HttpClient(); http.getData()
<a name="jaEXg"></a>#### 方法装饰器**它会被应用到方法的属性描述符上,可以用来监视,修改或替换方法定义。**<br />**方法装饰器会在运行时传入下列3个参数:**1. **对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。**1. **成员的名字。**1. **成员的属性描述符。**```typescriptfunction get(params:any) {return function(target:any, methodName:any, desc:any) {target.apiUrl = 'xxxxxx'target.run = function() {console.log('run')}}}class HttpClient {url:any | undefined;constructor() {}// 调用方法选择器@get('www.baidu.com')getData() {console.log(this.url)}}var http:any = new HttpClient()http.run()
// 修改当前类的方法function get(params:any) {return function(target:any, methodName:any, desc:any) {// 1. 保存当前的方法var oMethod = desc.valuedesc.value = function(...arrs:any[]) {arrs = arrs.map(value => {return String(value);})// 对象冒充oMethod.apply(this, arrs)}}}class HttpClient {constructor() {}getData(...args:any[]) {console.log(arrs)console.log('我是getData里面的方法')}}var htto = new HttpClient()http.getData(123, 'xxx')
方法参数装饰器
参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 参数的名字
- 参数在函数参数列表中的索引 ```typescript function logParams(params:any) { return function(target:any, methodName:any, params:any) { target.apiUrl = params } } class HttpClient { constructor() {} // 调用方法参数装饰器 getData(@logParams(‘xxx’) uid:any) { console.log(‘uid’, uid) } }
var http = new HttpClient() htpp.getData(123456) console.log(http.apiUrl) ```
装饰器的执行顺序
属性 > 方法 > 方法参数 > 类
如果有多个同样的装饰器,它会先执行后面的装饰器
