说明
- 以JS为基础构建的语言
- 一个JS的超集
- TS扩展了JS,并添加了类型
- 可以在任何支持JS的平台中执行
- TS不能被JS解析器直接执行,需要编译成JS再执行
开发环境搭建
- 全局安装:
npm i typescript -g
- 创建ts文件,进入目录后使用
tsc
进行编译
类型声明
// 指定a的类型为boolean
let a: boolean;
a = true;
// a = "dudu"; // 会报错 不能将类型“string”分配给类型“boolean”
// 声明完变量直接赋值
let b: string = "dudu";
// 如果变量的声明和赋值写在一起,ts会自动对变量进行类型检测,第一次赋的值就会决定它的类型
let c = true;
c = false;
// c = 123; // 会报错 不能将类型“number”分配给类型“boolean”
// 用于限制函数参数的类型和返回值的类型
function sum(x: number, y: number): number {
return x + y;
}
console.log(sum(123, 456));
// sum(123, "456"); // 类型“string”的参数不能赋给类型“number”的参数
// sum(123, 456, 789); // 应有 0-2 个参数,但获得 3 个
字面量
// 同时确定类型和值,以后不能再修改
let a: "dudu";
// 联合类型
let a1: "man" | "woman";
a1 = "man";
a1 = "woman";
// a1 = "dudu"; // 不能将类型“"dudu"”分配给类型“"man" | "woman"”
let a2: boolean | number;
a2 = 123;
a2 = false;
// a2 = "dudu"; // 不能将类型“"dudu"”分配给类型“number | boolean”
任意类型 any
let a: any; // 或 let a;
a = 10;
a = "dudu";
a = true;
// any可以赋值给任意类型的变量,可能有些隐藏问题,不建议使用
let a1: string = "dudu";
console.log(a1); // dudu
a1 = a;
console.log(a1); // true
未知类型 unknown
let a: unknown;
a = 10;
a = true;
a = "dudu";
// unknown不允许赋值给其他变量
let a1: string = "dudu";
// a1 = a; // 报错,不能将类型“unknown”分配给类型“string”
// 可以判断完类型后再进行赋值
if (typeof a === "string") {
a1 = a;
}
console.log(a1);
// 也可以使用类型断言:通知编译器变量的实际类型
a1 = a as string; // 或 a1 = <string>a;
console.log(a1);
空 void
// 空表示没有值或者undefined
// 用来表示空,没有返回值
function fn(): void {
console.log("dudu");
// 可以 return; 可以 return null; 可以 return undefined;
}
没有值 never
// 表示永远不会返回结果
function fn(): never {
throw new Error("报错了!");
}
object
// 指定对象中只能包含哪些属性
// ?表示该属性可选
// [propName: string]: any 加上以后可以添加任意属性
let a: { name: string; age?: number; [propName: string]: any };
a = { name: "dudu", age: 123, gender: "man" };
// 设置函数结构的类型声明:指定参数个数和类型,返回值的类型
let fn: (a: number, b: number) => number;
fn = function (n1, n2) {
return n1 + n2;
};
array
// 声明个字符串数组
let a: string[]; // 或 let a: Array<string>;
a = ["a", "b", "c"];
tuple
// 元组:固定长度的数组
let a: [string, number];
a = ["s", 123];
enum
// 枚举,也可以直接声明变量,会自动进行赋值,从0开始
enum Gender {
man = 1,
woman = 0,
}
let person: { name: string; gender: Gender };
person = { name: "dudu", gender: Gender.man };
console.log(person.gender === Gender.man);
// 值和名字都可以获取
console.log(Gender.man, Gender[1]); // 1 man
其他说明
// &:对象要同时满足两个条件
let person: { name: string } & { gender: string };
person = { name: "dudu", gender: "man" };
// 类型的别名
type myType = 1 | 2 | 3 | 4 | 5;
let a: myType;
a = 3;
编译选项
编译并开启监视,在文件发生变化时自动编译
一个文件 tsc -w
多个文件 tsc -w
要注意:多个文件使用时要在项目目录中添加 配置文件
配置文件(在VSCode中可以在终端使用tsc --init
生成)
{
/* 指定哪些ts文件需要编译:两个**代表文件夹 一个*代表文件,即src下的所有目录下的所有文件 */
"include": ["./src/**/*"],
/*
指定哪些ts文件不需要编译
默认值为:["node_modules", "bower_components", "jspm_packages"]
*/
// "exclude": ["./src/test/**/*"],
/* 定义被继承的配置文件,继承该文件中所有的配置 */
// "extends": "./config/base",
/* 指定要被编译的文件列表,如果需要编译的文件比较少,可以这样指定 */
// "files": [""],
// 编译器的选项
"compilerOptions": {
/*
指定ts被编译为es的版本
默认值:'es3'
可选值:'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017',
'es2018', 'es2019', 'es2020', 'es2021', 'esnext'
*/
"target": "es2015",
/*
指定要是用的模块化的规范
可选值:'none', 'commonjs', 'amd', 'system', 'umd', 'es6',
'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'
*/
// "module": "es2015",
/*
用来指定项目中要使用的库
可选值:'es5', 'es6', 'es2015', 'es7', .... 可以填写错误的值根据提示查看可选值
*/
"lib": ["es6", "dom"],
// 用来指定编译后文件所在的目录
"outDir": "./dist",
// 将代码合并成一个文件
// "outFile": "./dist/",
// 是否对js文件进行编译,默认是false
"allowJs": true,
// 是否对js代码进行ts的语法检查,默认是false
"checkJs": true,
// 是否移除注释,默认是false
"removeComments": true,
// 不生成编译后的文件,默认是false
"noEmit": true,
// 当有错误时不生成编译后的文件,默认是false
"noEmitOnError": true,
// 所有严格检查的总开关
"strict": true,
// 用来设置编译后的文件是否使用严格模式,默认是false
"alwaysStrict": true,
// 不允许隐式any类型(不指定函数的参数类型是默认是any,此时any是隐式any)
"noImplicitAny": true,
// 不允许不明确类型的this(函数由于调用方式和严格模式的问题this不确定)
"noImplicitThis": true,
// 严格的检查空值
"strictNullChecks": true,
}
}
面向对象
1. 创建类
class Person {
// 静态属性:使用类名直接访问
static job: string = "program";
// 只读属性,只有构造函数中可以修改
readonly name: string;
// 实例属性
age: number;
// 构造函数:在对象创建时调用
constructor(name: string, age: number) {
// this表示当前的实例,就是当前正在创建的对象
this.name = name;
this.age = age;
}
sayHello() {
console.log(`我叫${this.name},我今年${this.age}岁了!`);
}
}
const p1 = new Person("LMY", 25);
p1.sayHello();
console.log(Person.job, p1.name, p1.age);
// = "dudu"; // 无法分配到 "name" ,因为它是只读属性。
2. 类的继承
class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say() {
console.log("动物在叫~");
}
}
/* 继承后,子类会拥有父类所有的方法和属性 */
class Dog extends Animal {
// 可以添加特有的属性和方法
skinColor: string;
constructor(name: string, age: number, skinColor: string) {
// 必须使用父类的构造函数,否则继承来的属性无法初始化
super(name, age);
this.skinColor = skinColor;
}
// 方法重写:子类方法覆盖父类方法的实现
say() {
// super表示当前类的父类
// ();
console.log("嗷呜~");
}
}
class Cat extends Animal {
say() {
console.log("喵呜~");
}
}
const d1 = new Dog("旺财", 4, "yellow");
const c1 = new Cat("一锅", 5);
d1.say();
c1.say();
console.log(d1);
多态
const a: Animal = new Animal("动物");
a.run(100);
const a1: Animal = new Dog("旺财");
a1.run(100); // 调用的是自己的run方法
const a2: Animal = new Cat("骆一锅");
a2.run(100); // 调用的是自己的run方法
3. 抽象类
/* 抽象类:不能用来创建对象 */
abstract class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 抽象方法:没有方法体,只能定义在抽象类中,且子类必须重写该方法
abstract say(): void;
}
4. 接口
/* 接口用来定义一个类的结构 */
interface myInterface {
name: string;
age?: number; // 该属性可以有也可以没有
}
// 接口可以重复声明,使用时是二者的并集
interface myInterface {
gender: string;
}
// 当成类型声明使用:对象要拥有所有的属性
const obj: myInterface = {
name: "dudu",
age: 22,
gender: "man",
};
/* 接口只定义对象的结构,属性都不可以有实际的值,方法都是抽象方法 */
interface myInterface1 {
name: string;
sayHello(): void;
}
// 可以限制类的结构,实现接口要拿到所有属性并实现所有方法,可以同时实现多个接口
class MyClass implements myInterface1 {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello(): void {
console.log("hello~");
}
}
// 可以限制函数的声明,类似一个只有参数列表和返回值类型的函数定义,参数列表里每个参数都需要名字和类型
interface ISearchFunc {
(source: string, subString: string): boolean;
}
// 子串是否存在
const searchString: ISearchFunc = function (
source: string,
subString: string
): boolean {
return source.indexOf(subString) > -1;
};
console.log(searchString("dudu", "u"));
- 接口可以继承其他的多个接口
- 一个类可以实现多个接口
5. 属性的封装
class Person {
// public private
// protected 只能在当前类和当前类的子类中访问
// 可以直接在构造函数中声明属性
constructor(private _name: string, private _age: number) {
this._name = _name;
this._age = _age;
}
// 这样的写法可以 正常使用 '.' 来处理属性
get age() {
return this._age;
}
get name() {
return this._name;
}
set name(value: string) {
this._name = value;
}
set age(value: number) {
if (value > 0 && value < 120) {
this._age = value;
}
}
sayHello() {
console.log(`我叫${this._name},我今年${this._age}岁了!`);
}
}
const p1 = new Person("LMY", 25);
p1.sayHello();
p1.age++;
console.log(p1.name);
console.log(p1);
6. 泛型
/* 在定义函数或是类时,如果遇到类型不明确的就可以使用泛型 */
// function fn(a: number): number {
// return a;
// }
function fn<T>(a: T): T {
return a;
}
// 直接调用
console.log(fn(1));
// 在调用的时候指定类型
console.log(fn<number>(1));
// 可以指定多个泛型
function fn2<T, K>(a: T, b: K): T {
console.log(b);
return a;
}
console.log(fn2(123, "dudu"));
console.log(fn2<number, string>(123, "dudu"));
// 限定泛型的类型
interface MyIn {
length: number;
}
function fn3<T extends MyIn>(a: T): number {
return a.length;
}
// 类也可以使用泛型
class MyClass<T> {
name: T;
constructor(name: T) {
this.name = name;
}
}
const mc = new MyClass<string>("dudu");
函数
函数的完整写法
const add: (x: number, y: number) => number = function ( x: number, y: number ): number {
return x + y;
};
可选参数和默认参数
// 第一个参数为默认参数,第二个参数为可选参数
function getFullName(firstName: string = "费", lastName?: string): string {
return lastName ? firstName + "_" + lastName : firstName;
}
console.log(getFullName());
剩余参数
// 多余的参数都放在args数组中
function fn(x: number, ...args: number[]) {
console.log(x, args);
}
fn(1, 2, 3, 4, 5);
函数重载
// 函数声明重载
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: number | string, y: number | string): number | string {
if (typeof x === "number" && typeof y === "number") {
return x + y;
}
if (typeof x === "string" && typeof y === "string") {
return x + y;
}
}
console.log(add(2, 3));
console.log(add("2", "3"));
// 如果不声明重载,下面两个函数调用没有问题,声明后ts会报错,因为不符合重载声明的规则
// (add("2", 3));
// (add(2, "3"));
泛型
function getArr<T>(value: T, count: number): T[] {
let arr: T[] = [];
for (let i = 0; i < count; i++) {
arr.push(value);
}
return arr;
}
console.log(getArr(1, 5));
console.log(getArr("dudu", 5));
多个泛型参数的函数
function getArr<T, V>(x: T, y: V): [T, V] {
return [x, y];
}
const arr = getArr("dudu", 5.12586);
console.log(arr[0].split(""));
console.log(arr[1].toFixed(1));
泛型接口
interface ICrud<T> {}
// 参考Java
泛型类
class Crud<T> {}
//参考Java
泛型约束
interface ILength {
length: number;
}
// 由于传入的属性未必会有length属性,因此使用接口来约束
function getLength<T extends ILength>(x: T): number {
return x.length;
}
console.log(getLength("dudu"));
// (getLength(123)); // 由于没有length属性,会报错
声明文件
当使用第三方库时,需要引用声明文件才能获得对应的代码补全,接口提示等功能,ts会自动解析项目中所有的声明文件
在文件夹中创建
// 定义操作
declare var jQuery: (selector: string) => any;
此时无需在其他的 ts 文件中引入,在书写时就可以得到提示信息
也可以下载声明文件:npm i @types/jquery --save-dev,文件在node_modules下的types文件夹中,此时再应用也会有相应提示