您现在的位置是:首页 >技术杂谈 >一文学会TypeScript网站首页技术杂谈
一文学会TypeScript
TypeScript笔记
第一章 TypeScript简介
1.1、TypeScript简介
TypeScript 是由微软开发的一款开源的编程语言,TypeScript 是 Javascript 的超集,遵循最新的 ES6、ES5 规范,TypeScript 扩展了 JavaScript 的语法。TypeScript 更像后端 Java这样的面向对象语言,可以让 JavaScript 开发大型企业项目。谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+ 就是基于 Typescript 语法,最新的 Vue 3.0也已经全面支持TypeScript。
一张图描述 TypeScript 和 JavaScript 之前的关系:
1.2、TypeScript安装
打开CMD命令行,输入以下代码:
npm install -g typescript
1.3、TypeScript项目初始化
打开CMD命令行,输入以下代码:
mkdir ts-demo cd ts-demo tsc --init
初始化成功的话会在你的项目文件夹中有一个tsconfig.json
1.4、Hello TypeScript
-
1.新建一个Hello.ts
-
2.输入
*console*.log("Hello, TypeScript!!!")
,打印一句提示 -
3.将ts编译成js文件
在命令行中输入
tsc *.ts watch
它会开启一个监视器,监视当前文件夹下所有以.ts结尾的文件,当发生改变的时候,自动将它编译成js文件
-
4.查看编译好的js文件
-
5.需要使用可以像js文件一样在html文件中引入
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello TS</title> </head> <body> <script src="./Hello.js"></script> </body> </html>
-
6.成功输出 Hello TS
-
7.也可以直接使用node输出(node是可以直接用ts文件的,但其他它也是将ts转译为js使用的)
注意:html不能直接使用ts文件,需要将ts文件转译为可以直接使用的js文件
第二章 TypeScript数据类型
变量格式一:
let 变量名: 变量类型 = 初始化值;
变量格式二:
let 变量名: 变量类型 | undefined;
变量名 = 变量值;
- 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
- 所以如果你的变量的声明和赋值是同时进行的,可以省略掉类型声明
2.1、TypeScript的类型
类型 | 例子 | 描述 |
---|---|---|
number | 1,-1,2.5 | 任意数字 |
string | ‘hi’,“hi”,hi | 任意字符串 |
boolean | true,false | 布尔值true或false |
字面量 | 即常量,如1,“hi” | 限制变量的值必须为字面量的值 |
any | * | 任意类型 |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值,例如直接抛出异常 |
object | {name:“小A”,age: 12} | 可以为任意的JS对象 |
array | [1,2,3] | 任意的js数组 |
tuple | [4,5] | 元组,TS新增类型,固定长度的数组 |
enum | enum{ A , B} | 枚举,TS新增类型 |
2.2、字面量类型
-
字面量类型就是用字面量进行类型声明
-
语法:
let a: 10;
//可以直接用字面量进行类型声明 let a: 10; //字面量类型声明也需要赋值 // console.log(a) a = 10; //因为a使用字面量声明,所以 a只能为10 // a = 11
2.3、联合类型
-
可以使用
|
或&
来连接多个类型 -
|
表示满足其中一个就可以let b: "ok" | "error" b = "ok"; //当使用了联合类型,那么值就只能在联合类型中间选择 // b="warn"
-
&
表示需要全部满足//& 用于对象 类型,需要同时满足两种情况 let x:{name:string}&{age:number} x = {name:"123",age:1}
2.4、any 与 unknown
-
any表示类型不确定,所以就不会有类型限制,虽然灵活,但是却非常容易给后续的赋值使用中带来bug
//这种没有指定类型的,也没有赋值的,默认会为any类型,这是隐式any let d; d = "123" //any 显式声明 let e: any; e = false //any类型可以给任何变量赋值(报错的主要原因) c = e;
-
unknown表示位置的类型,他也可以接收任意类型的值,但是赋值给其他变量
不能直接赋值
,需要有类型推断的断言let fa: unknown fa = "123"; fa = 1 fa = true; //赋值 方法1 利用typeof 进行类型判断赋值 if (typeof fa == "string") { c = fa } //赋值 方法2 利用断言 c = fa as number c = <number>fa
2.5、类型断言
-
类似于Java中的强制类型装换
用来告诉程序某个不确定类型的变量是什么类型
/** * 类型断言 * 1.利用as * 2.利用<type> */ c = fa as number c = <number>fa
2.6、void 与 never
-
这两个类型主要针对函数的返回值
-
void 表示没有值或者undefined
//返回值类型 void 没有值或undefined function fn(): void { console.log("123") // return undefined; }
-
never 表示没有返回值,什么都没有
function fn2(): never { throw new Error("error 123") }
2.7、object类型
-
在js中除了基本类型都是object类型
-
语法:
let a: object; a = {}; a = function () { } /** * {} 用来指定对象中可以包含哪些属性 * 语法:{属性名:属性值,属性名:属性值} * 在属性名后加上?表示属性是可选的 */ let b: { name: string, age?: number } b = {name: "abc", age: 18} //[propName:string]:any 表示可以增加任意名称的任意类型的属性 let c: { name: string, [propName: string]: any } c = {name: "ccc", age: 18, sex: "男"}
2.8、函数类型声明
-
语法:*(形参,形参)**=>*返回值类型
let d: (a: number, b: number) => number; d = function (n1, n2) { return n1 + n2 }
2.9、数组与元组
-
数组类型
/** * 设置数组类型 * string[] * Array<string> */ let e: string[]; e = ["1", "2", "3"] let g: Array<number> g = [1, 2, 3];
-
元祖:就是长度固定的数组
/** * 元组:就是固定长度的数组 tuple */ let h: [string, number] h = ["1", 2]
2.10、枚举
-
可以用来定义一些同种类的常量
enum Sex { Male = 0,//如果不定义值,它的值就是下标。 Female = 1 } let j: { name: string, [propName: string]: any } j = {name: "ccc", age: 18, sex: 0} if (j.sex == Sex.Male) { console.log("男", j) } else { console.log("女") }
2.11、类型别名
-
通过定义类型别名可以更方便的多次使用统一的类型约束
type objType = {name:string}&{age:number}; let y :objType={name:"123",age:1}
第三章 TypeScript函数
3.1、函数定义
函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。
通常情况下,TypeScript 代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。
3.2、函数格式
-
函数格式一:
-
function 函数名(参数列表): 返回值类型 { 函数体 ... [return 返回值;] }
-
函数格式二:
-
let 函数名 = function (参数列表): 返回值类型 { 函数体 ... [return 返回值;] };
3.3、必选参数
必选参数:在调用函数的时候,必须要传入的参数,参数列表里边的参数默认就是必选参数,只要在声明的时候写了参数,在传递的时候,就必须传入参数,而且,实参与形参的数量与类型要一致。
function getInfo(name: string, age: number): string {
return `${name} --- ${age}`;
}
console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 错误
console.log(getInfo(28)); // 错误
3.4、可选参数
可选参数:为了解决在函数传参的时候,某些参数可以不用传递,我们就需要可选参数了。
function getInfo(name: string, age?: number): string {
return `${name} --- ${age}`;
}
console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误
注意:可选参数必须配置到参数的最后面。
3.5、默认参数
默认参数:为了解决在函数传参的时候,某些参数可以不用传递,但是我们又需要该参数的值,这时候我们就需要给这个参数设定一个默认值也叫初始化值,就得用到默认参数了。
function getInfo(name: string, age: number = 20): string {
return `${name} --- ${age}`;
}
console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误
注意:可选参数不能够进行初始化值的设定。
3.6、剩余参数
剩余参数:在参数的类型确定而参数个数不确定的情况时,我们需要用到剩余参数,它使用 … 将接收到的参数传到一个指定类型的数组中。
function sum(...result: number[]): number {
let sum = 0;
for (let i = 0; i < result.length; i++) {
sum += result[i];
}
return sum;
}
console.log(sum(1, 2, 3, 4, 5, 6));
function sum(init: number, ...result: number[]): number {
let sum = init;
for (let i = 0; i < result.length; i++) {
sum += result[i];
}
return sum;
}
console.log(sum(100, 1, 2, 3, 4, 5, 6));
注意:剩余参数必须配置到参数的最后面。
3.7、重载函数
重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。
TypeScript 中的重载是通过为同一个函数提供多个函数类型声明来实现函数重载的功能的。
//重载函数声明
function getInfo(name: string): string;
function getInfo(name: string, age: number): string;
//重载函数签名:就是把声明中出现的参数都写出来,如果可选,就使用可选参数,一个变量名可以使用多种类型用组合类型
function getInfo(name: string, age?: string | number): string {
if (age) {
return "我叫:" + name + ",年龄:" + age;
} else {
return "我叫:" + name;
}
}
console.log(getInfo("zhangsan"));// 正确
console.log(getInfo("lisi", 20));// 正确
console.log(getInfo(123));// 错误
第四章 TypeScript类
4.1、类的定义
class Person {
//readonly开头的属性表示一个只读的属性,无法修改
private readonly name:string
private age:number
//在属性前使用static关键字可以定义类属性(静态属性)
static type:string = "人类"
constructor(name:string,age:number) {
this.name=name;
this.age = age;
}
public sayHello(){
console.log("hello",this)
}
}
let person = new Person("孙悟空",5000);
console.log(Person,person);
person.sayHello()
4.2、修饰符
TypeScript 里面定义属性的时候给我们提供了 三种修饰符
- public:公有类型,在当前类里面、子类、类外面都可以访问
- protected:保护类型,在当前类里面、子类里面可以访问,在类外部没法访问
- private:私有类型,在当前类里面可以访问,子类、类外部都没法访问
注意:如果属性不加修饰符,默认就是公有(public)。
4.3、静态属性与静态方法
与java相同,静态属性与方法都是通过 类名.
的方式使用的
class Person {
name: string;//属性,前面省略了public关键词
static sex: string = "男";//被静态修饰符static修饰的属性
constructor(n: string) {//构造函数,实例化类的时候触发的方法
this.name = n;
}
run(): void {//方法
console.log(this.name+ "在跑步");
}
static print(): void {//被静态修饰符static修饰的方法
// console.log('姓名:' + this.name);//错误
console.log('性别:' + Person.sex);//正确
// this.run();//错误
}
}
//调用Person的静态属性
console.log(Person.sex);
//调用Persong的静态方法
Person.print()
4.4、类的封装
有时候我们不希望外部直接通过类名.属性
的方式直接修改使用实例对象中的属性值。这时候我们就需要去封装我们的类属性,对外不暴露属性,通过暴露对应的get或set方法,在方法内部对需要使用的属性进行逻辑判断。
class Person {
private _age: number;
private _name: string;
constructor(age: number, name: string) {
this._age = age;
this._name = name;
}
say(): void {
console.log("你好呀")
}
/**
* ts提供的写法,可以直接用名称访问
*/
get age(): number {
return this._age;
}
set age(value: number) {
this._age = value;
}
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
4.5、类的继承
类的继承:在 TypeScript 中要想实现继承使用 extends
关键字,只要一旦实现了继承关系,那么子类中便拥有了父类的属性和方法,而在执行方法过程中,首先从子类开始找,如果有,就使用,如果没有,就去父类中找。类的继承只能单向继承(只能有一个父亲)
。
export class Dog extends Animal{
constructor(name: string, age: number) {
super(name, age);
}
fox(): void {
console.log(this.name+"叫:汪汪汪")
}
}
class TaiDi extends Dog{
/**
* 重写方法
*/
fox(): void {
console.log(this.name+"叫:我是泰迪")
}
}
4.6、抽象类
TypeScript 中的抽象类:它是提供其他类继承的基类,不能直接被实例化。
用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类(也就是其子类)中实现,abstract抽象方法只能放在抽象类里面。
//动物抽象类,所有动物都会叫(假设),但是叫的声音不一样,所以把叫的方法定义成抽象方法
export abstract class Animal implements IAnimalBehavior {
private _name: string;
private _age: number
protected constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
abstract fox(): void;
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
get age(): number {
return this._age;
}
set age(value: number) {
this._age = value;
}
}
//利用其他类去实现
class Dog extends Animal{
constructor(name: string, age: number) {
super(name, age);
}
fox(): void {
console.log(this.name+"叫:汪汪汪")
}
}
class TaiDi extends Dog{
/**
* 泰迪重写方法
*/
fox(): void {
console.log(this.name+"叫:我是泰迪")
}
}
4.6、类的多态
多态:父类定义一个方法不去实现,让继承它的子类去实现 ,每一个子类有不同的表现,多态属于继承
。
例子同上,每一个继承Animal的类都需要实现fox方法,每一个动物的叫声实现都不一样,就实现了多态。
第五章 TypeScript接口
5.1、接口的定义
在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
5.2、接口的用途
接口的用途就是对行为和动作进行规范和约束,跟抽象类有点像,但是,接口中不能有方法体,只允许有方法定义。
5.3、属性类型接口
//对传入对象的属性约束,以下这个是一个属性接口
interface FullName {
firstName: string;
secondName: string;
}
//对方法的传入参数进行限制
function printName(name: FullName) {
console.log(name.firstName + "--" + name.secondName);
}
//传入的参数必须包含firstName、secondName
var obj = {
age: 20,
firstName: '张',
secondName: '三'
};
printName(obj);//正确
// printName("1213");//错误传入
5.4、函数类型接口
这种主要是对方法的声明的类型限制,后续传入对应方法的时候也要满足这个限制才行。
//加密的函数类型接口
interface encrypt {
(key: string, value: string): string;
}
var md5: encrypt = function (key: string, value: string): string {
//模拟操作
return key + "----" + value;
}
console.log(md5("name", "zhangsan"));
5.5、可索引型接口
可索引接口就是对数组、对象的约束,不常用。
//可索引接口,对数组的约束
interface UserArr {
[index: number]: string
}
var arr1: UserArr = ["aaa", "bbb"];
console.log(arr1[0]);
//可索引接口,对对象的约束
interface UserObj {
[index: string]: string
}
var arr2: UserObj = { name: '张三', age: '21' };
console.log(arr2);
5.6、类类型接口
类类型接口就是对类的约束,它和抽象类抽象有点相似。
interface IAnimalBehavior {
//动物叫方法
fox: () => void
}
export abstract class Animal implements IAnimalBehavior {
private _name: string;
private _age: number
protected constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
//需要实现的接口中的方法
abstract fox(): void;
}
5.7、接口的继承
接口可以继承接口,接口之间和抽象类之间的继承都是单向单继承,但是实现接口的子类可以实现多个接口。
简单来说,对于类、抽象类、接口继承只能单继承,但接口却可以多实现(一个类可以同时实现多个接口)。
//人这个接口
interface Person {
eat(): void;
}
//程序员接口
interface Programmer extends Person {
code(): void;
}
//小程序接口
interface Web {
app(): void;
}
//前端工程师
class WebProgrammer implements Person, Web {
public name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log(this.name + "下班吃饭饭")
}
code() {
console.log(this.name + "上班敲代码");
}
app() {
console.log(this.name + "开发小程序");
}
}
var w = new WebProgrammer("小李");
w.eat();
w.code();
w.app();
第六章 TypeScript泛型
6.1、泛型的定义
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。
通俗理解:泛型就是解决类、接口、方法的复用性、以及对不特定数据类型的支持。
6.2、泛型类
泛型类可以支持不特定的数据类型,要求传入的参数和返回的参数必须一致,T表示泛型,具体什么类型是调用这个方法的时候决定的。
//类的泛型
class MinClas<T>{
public 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代表的类型是number
var m1 = new MinClas<number>();
m1.add(11);
m1.add(3);
m1.add(2);
console.log(m1.min());
//实例化类并且制定了类的T代表的类型是string
var m2 = new MinClas<string>();
m2.add('c');
m2.add('a');
m2.add('v');
console.log(m2.min());
6.3、泛型接口
//泛型接口
interface ConfigFn<T> {
(value: T): T;
}
function getData<T>(value: T): T {
return value;
}
var myGetData: ConfigFn<string> = getData;
console.log(myGetData('20'));
6.4、泛型类接口(泛型传入的对象)
//定义操作数据库的泛型类
class MysqlDb<T>{
add(info: T): boolean {
console.log(info);
return true;
}
}
//想给User表增加数据,定义一个User类和数据库进行映射
class User {
username: string | undefined;
pasword: string | undefined;
}
var user = new User();
user.username = "张三";
user.pasword = "123456";
var md1 = new MysqlDb<User>();
md1.add(user);
第七章 TypeScript命名空间
-
命名空间:在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内,同Java的包、.Net的命名空间一样,TypeScript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象,命名空间内的对象通过export关键字对外暴露。
-
命名空间和模块的区别:
- 命名空间:内部模块,主要用于组织代码,避免命名冲突。
- 模块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。
-
namespace A { interface Animal { name: string; eat(): void; } export class Dog implements Animal { name: string; constructor(theName: string) { this.name = theName; } eat(): void { console.log(`${this.name} 吃狗粮。`); } } export class Cat implements Animal { name: string; constructor(theName: string) { this.name = theName; } eat(): void { console.log(`${this.name} 吃猫粮。`); } } } namespace B { interface Animal { name: string; eat(): void; } export class Dog implements Animal { name: string; constructor(theName: string) { this.name = theName; } eat(): void { console.log(`${this.name} 吃狗粮。`); } } export class Cat implements Animal { name: string; constructor(theName: string) { this.name = theName; } eat(): void { console.log(`${this.name} 吃猫粮。`); } } } var ac = new A.Cat("小花"); ac.eat(); var bc = new B.Cat("小花"); bc.eat();