TypeScript与JavaScript的关系
- TypeScript是一个基于JavaScript的扩展语言
- TypeScript包含了JavaScript的所有内容
- TypeScript增加了:静态类型检查、接口、泛型等很多现代开发特性,更适合大型项目。
- TypeScript需要编译为JavaScript,然后才能在浏览器或其他环境运行。
为什么需要TypeScript
- JavaScript 当年诞生时的定位是浏览器脚本语言,用于在网页中嵌入简单的逻辑,且代码量很少。
- 随着时间的推移,JavaScript 变得越来越流行,如今的 JavaScript 已经可以全栈编程
- 现如今的 JavaScript 应用场景比当年丰富的多,代码量也比当年大很多,随便一个JavaScript 项目的代码量,可以轻松的达到几万行,甚至十几万行!
- 然而 JavaScript 当年“出生简陋”,没考虑到如今的应用场景和代码量,逐渐就出现了很多困扰
JavaScript中的困扰
- 不清不楚的数据类型
- 有逻辑的漏洞
- 访问不存在的属性
- 低级的拼写错误
静态类型检查
- 在代码运行前进行检查,发现代码的错误或不合理之处,减小运行时出现异常的几率,此种检查叫『静态类型检查』,TypeScript 和核心就是『静态类型检查』,简言之就是把运行时的错误前置。
- 同样的功能,TypeScript 的代码量要大于 JavaScript,但由于 TypeScript 的代码结构更加清晰,在后期代码的维护中 TypeScript 却胜于 JavaScript。
编译TypeScript
浏览器不能直接运行TypeScript代码,需要编译为JavaScript再交由浏览器解析器执行。
命令行编译
- 创建demo.ts文件
const person = {name: '张三',age: 10
}
console.log(`我叫${person.name}, 今年:${person.age}岁`);
- 安装TypeScript
npm i typescript -g
- 编译TypeScript
tsc demo.ts
自动化编译
- 创建TypeScript编译控制文件
tsc --init
- 监视目录中的 .ts 文件变化
tsc --watch 或 tsc --w
- 编译出错时,不生成js文件
在tsconfig.json文件中,放开配置的注释即可。
"noEmitOnError": true或在启动时添加
tsc --noEmitOnError --watch
类型声明
声明格式
let 变量:类型
变量-设置类型
let a:string //只能存字符串类型
let b:number //只能存数字类型
let c:boolean //只能存布尔类型a = "hello";
//a = 100 不能把100赋值给a
b = 10;
c = true
注意:类型必须声明后才能使用,否则会报错。
方法-设置类型
// 设置参数类型和返回值类型
function sum(a: number, b: number):number{return a + b;
}
sum(1,2);//调用sum时,必须传入数字类型
字面量类型
let x:'hello';
x = 'hello';
x只能为'hello'。
类型推断
TypeScript会根据我们的代码进行类型推导。
let d = 'abc';
d = 9;//类型不同,赋值会报错
变量d的类型为字符串,不能赋值数字
注意:类型推断面对复杂类型时容易出问题,所以还是要明确的编写类型声明。
类型总览
JavaScript数据类型
- string
- number
- boolean
- null
- undefined
- bigint
- symbol
- object
object类型又包括:Array,Function,Date等。。。
TypeScript数据类型
- 包含JavaScript的数据类型
- 新增6个类型
-
- any
- unkown
- never
- void
- tuple
- enum
- 两个自定义类型的方式
-
- type
- interface
包装类型
在JavaScript中的这些内置构造函数:String, Number, Boolean用于创建对应的包装类型,在日常开发中很少使用。因此在TypeScript中进行类型声明,通常使用的是小写的:string, number,boolean这种原生数据类型。
let test:string
test = "abc"
// test = new String();let test1:String
test1 = "def"
test1 = new String();console.log(typeof test); //输出:string
console.log(typeof test1); //输出:object
原始类型 VS 包装类型
- 原始类型:如 number、string、boolean ,在 JavaScript 中是简单数据类型,它们在内存中占用空间少,处理速度快。
- 包装对象:如 Number 对象、String 对象、 Boolean 对象,是复杂类型,在内存中占用更多空间,在日常开发时很少由开发人员自己创建包装对象。
- 自动装箱:JavaScript 在必要时会自动将原始类型包装成对象,以便调用方法或访问属性。
代码
let str = 'hello'
console.log(str.length);
内部执行逻辑:
常用类型与语法
any
any表示任意类型,一旦变量设置为any,那就意味着放弃了对该变量的类型检查。
//变量ab,可以赋予任何类型的值
let ab:any
ab = 'a'
ab = 9//变量bb,如果不声明类型,TypeScript推断出来的类型就是any(隐式的any)
let bb
bb = 'b'
bb = 10//any类型的变量可以赋值给任意类型的变量
let cb:string
cb = ab
nuknown
unknown是未知类型,适用于:起初不确定数据的具体类型,需要后期才确定。
- unknown可以理解为类型安全的any
let k:unknown
k = 1
k = true
k = "hello"//不能将unknown类型赋值给string类型
// let ka:string
// ka = k//unknown转换为指定类型
let ka:string
ka = 'str';
//方式1:类型判断
if(typeof k === 'string'){ka = k;console.log(ka);
}//方式2:断言
ka = k as string;
console.log(ka);//方式3:转换类型
ka = <string> k;
console.log(ka);
注意:要想使用unknown类型的变量,必须将其转换为具体类型。否则,读取变量的属性、方法会报错
never
never表示:任何值都不是,即:不能有值。例如:undefined,null,‘’都不行。
- never几乎不会用来直接限制变量
let n1:never
此时,n1不能再设置任何值
- never是TypeScript推断出来的
let n2: string
n2 = 'hello'
if (n2 === 'string') {console.log(n2.toUpperCase());
} else {console.log(n2);//这里推断出来的类型就是never
}
- 限制函数返回值
function throwError(str: string): never {throw new Error(str);
}
throwError("abc");//调用时就不用尝试获取其返回值了
void
void的含义是空,即函数不返回任何值,调用者也不应该依赖其返回值进行任何操作。
- void通常用于函数的返回值声明
function logMessage(msg: string): void {console.log(msg);
}
console.log("hello world");
- 无返回值也可以这样写
function logMessage1(msg: string): void {console.log(msg);return;
}function logMessage2(msg: string): void {console.log(msg);return undefined;
}
- 返回void与undefined的区别
返回void时,不能操作返回值
function returnVoid(msg: string): void {console.log(msg);
}
let voidResult = returnVoid("abc");
//尝试操作返回值,会报错
if (voidResult) {console.log(voidResult);
}
返回undefined时,可以操作返回值
function returnUndefined(msg: string): undefined {console.log(msg);return undefined;
}
let undefinedResult = returnUndefined("test");
if (undefinedResult) {console.log(undefinedResult);
}
理解 void 与 undefined
- void 是一个广泛的概念,用来表达“空”,而:undefined:则是这种“空”的具体实现。
- 因此可以说:undefined是 void 能接受的一种“空”的状态。也可以理解为: void 包含 undefined:,但 void 所表达的语义超越了 undefined ,void 是一种意图上的约定,而不仅仅是特定值的限制。
总结
如果一个函数返回类型为 void ,那么:
- 从语法上讲:函数是可以返回 undefined 的,至于显式返回,还是隐式返回,这无所谓!
- 从语义上讲:函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!即使我们知道它返回了 undefined
object
实际开发中使用较少,因为范围太大。
object(小写)
所有非原始类型,可存储:对象,函数,数组等。
let o: object
o = {}
o = [1, 2, 3]
o = function () { }
o = new String("abc")
class Person { }
o = new Person();o = 1 //报错,这是原始类型
Object(大写)
除了 null 和 undefined 的任何值。
let O: Object
O = 1
O = true
O = "abc"O = null //报错,null不能赋值给Object
O = undefined //报错,undefined不能赋值给Object
声明对象类型
- 实际开发中,限制一般对象,通常使用以下形式。
let user: { name: string, age?: number }
let user1: { name: string; age?: number }
let user2: {name: string;age?: number
}//给类型赋值
user = { name: "zhangsan", age: 10 }
user1 = {name: "lisi"}
user2 = {name: "wangwu", age:"aab"} //会报错,age类型不是number
- 索引签名:允许定义对象具有任意数量的属性,这些属性的键与值是可变的。常用与:描述类型不确定的属性。
let user4: {name: string,age?: number,[key: string]: any
}
user4 = {name: "zs",age: 12,like: 'ball'
}
声明函数类型
方式1:TypeScript 中的 =>在函数类型声明时表示函数类型,描述其参数类型和返回类型
let sum1: (a: number, b: number) => number
sum1 = function (a, b) {return a + b
}
这种方式定义的函数不能提升(hoisted),不能在声明前被调用
方式2:
console.log(sum2(1, 2));
function sum2(a: number, b: number): number {return a + b
}
这种方式定义的函数可以在声明前被调用,函数会被提升(hoisted)
方式3:箭头函数声明时定义
let sum3 = (a: number, b: number): number => a + b;
console.log(sum3(1,2));
声明数组类型
let arr1: string[]
let arr2: Array<string>arr1 = ['1', '2']
arr2 = ['3', '4']
tuple-元组
元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组用于精确描述一组值的类型,?表示可选元素。
//第一个元素必须是string类型,第二个元素必须是number类型
let tp1: [string, number];
tp1 = ["a", 2];
//第一个元素必须是string类型,第二个元素可为空,如果存在,必须是boolean类型
let tp2: [string, boolean?];
tp2 = ['a', true]
//第一个元素必须是number类型,后面的元素可以是任意数量的string类型
let tp3: [number, ...string[]]
tp3 = [1, "a","b"]
enum-枚举
枚举 (enum)可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护。
定义枚举
enum Direction {Up,Down,Left,Right
}
console.log(Direction);
console.log(Direction.Up);// 输出0
console.log(Direction[0]);// 输出Up
枚举中的元素的值是从0开始递增的。
enum Direction {Up,Down = 5,Left,Right
}
可以在初始化枚举时设置值,设置值后面的会递增,前面的不会(从0开始)
使用枚举的案例
未使用枚举
function direction(str: string) {if (str === 'up') {console.log("上");} else if (str === 'down') {console.log("下");} else if (str === 'left') {console.log("左");} else if (str === 'right') {console.log("右");}
}
console.log(direction1(up));
使用枚举
enum Direction {Up,Down,Left,Right
}function direction1(str: Direction) {if (str === Direction.Up) {console.log("上");} else if (str === Direction.Down) {console.log("下");} else if (str === Direction.Left) {console.log("左");} else if (str === Direction.Right) {console.log("右");}
}console.log(direction1(Direction.Left));
字符串枚举
enum Direction1 {Up = 'up',Down = 'down',Left = 'left',Right = 'right'
}
let d1:Direction1 = Direction1.Down;
console.log(d1);
常量枚举
官方描述:常量枚举是一种特殊枚举类型,它使用const 关键字定义,在编译时会被内联,避免生成一些额外的代码。
何为编译时内联?
所谓“内联”其实就是 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值而不是生成额外的枚举对象。这可以减少生成的 JavaScript 代码量,并提高运行时性能。
const enum Direction2 {Up,Down,Left,Right
}
console.log(Direction2.Down);
type
type可以为任意类型创建别名,让代码变得简洁、可读性更强,同时能更方便的进行类型复用和扩展
基本用法
类型别名使用 type 关键字定义, type 后跟类型名称,例如下面代码中 num:是类型别名。
type num = number;
let count: num;
count = 1;
console.log(count);
联合类型
联合类型是一种高级类型,它表示一个值可以是几种不同类型之一。
type Status = number | string;
function printStatus(status: Status) {console.log(status);
}
printStatus('abc');
printStatus(123);
传入的参数可以是number也可以是string。
交叉类型
交叉类型(intersection Types)允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。
type Area = {width: number;heigth: number;
}type Address = {cell: number;//单元号room: string;//房间号
}type House = Area & Address;
const house: House = {width: 1,heigth: 2,cell: 3,room: "001"
}
console.log(house);
特殊情况
//定义一个类型,这个类型是一个无返回值的函数。.
type Func = () => void;//TypeScript并不限制其返回
const myFunc: Func = () => {console.log("abc");return "abc";
}
myFunc();//Func类型作为参数使用
function testMyFuncParam(f: Func) {f();
}
testMyFuncParam(() => {console.log("testMyFunc");
});//Func类型作为返回值使用
function testMyFuncReturn(): Func {return () => {console.log("no return");}
}
testMyFuncReturn()();
类
定义类
class User {//声明属性name: stringage: numberconstructor(name: string, age: number) {this.name = name;this.age = age;}sayHi() {console.log(`你好,我是:${this.name},年龄:${this.age}`);}
}
let us = new User("小明", 10);
us.sayHi();
构造函数简写
constructor(public name: string,public age: number) {}
类的继承
class Vip extends User {grade: number;constructor(name: string, age: number, grade: number) {super(name, age);this.grade = grade;}override sayHi(): void {console.log(`我是:${this.name} 年龄:${this.age} 年级:${this.grade}`);}study() {console.log(`${this.name}努力学习中。。。`);}
}
属性修饰符
public
属性默认是被public修饰。
class User {//声明属性public name: stringage: numberconstructor(name: string, age: number) {this.name = name;this.age = age;}sayHi() {console.log(`你好,我是:${this.name},年龄:${this.age}`);}
}
let us = new User("小明", 10);
console.log(us.name);
protected
父类定义方法
protected getName() {console.log(`我是:${this.name}`);
}
子类使用
getParentName() {super.getName();
}
private
父类创建私有属性:id
constructor(public name: string,public age: number,private id: number) {
}
并提供私有方法
private getId() {return this.id;
}
然后再提供一个public方法
getIdInfo() {console.log('我的id是:' + this.getId());
}
最后使用
let us = new User("张三", 30, 1001);
us.getIdInfo();
注意:不能直接使用new出来的对象调用私有方法。
readonly
class Car {constructor(public readonly vin: string,//车辆识别码public readonly year: number,//出厂年份public color: string) {}displayInfo() {console.log(`识别码:${this.vin}出厂年份:${this.year}颜色:${this.color}`);}
}let car = new Car('JW2222FJI04832', 2024, 'red');
car.displayInfo();
car.vin = '123'//只读属性不能修改
抽象类
概述:类是一种无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法。
简记:抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法、也可以有抽象方法。
//抽象包裹类
abstract class Package {constructor(public weight: number) {}abstract calculate(): number;printPackage() {console.log(`包裹重量:${this.weight} 费用:${this.calculate()} 元`);}
}//标准包裹类
class StdPackage extends Package {constructor(weight: number) {super(weight)}calculate(): number {return 1;}
}
let std = new StdPackage(2);
std.printPackage();
总结:何时使用抽象类?
- 定义通用接口:为一组相关的类定义通用的行为(方法或属性)时。
- 提供基础实现:在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现。
- 确保关键实现 : 强制派生类实现一些关键行为。
- 共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。
接口
interface 是一种定义结构的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意 interface 只能定义格式,不能包含任何实势
定义类结构
interface UserInterface {name: stringage: numbersayHi(n: number): void
}class MyUser implements UserInterface {constructor(public name: string,public age: number) { }sayHi(n: number): void {console.log(`你好,我是${this.name} 年龄是:${this.age} 我想说:${n}`);}
}
let myUser = new MyUser("李四", 20);
myUser.sayHi(666);
定义对象结构
interface UserInterface1 {name: stringreadonly gender: stringage?: numberrun: (n: number) => void
}
const myUser1: UserInterface1 = {name: "王五",gender: "男",age: 17,run(n) {console.log(`你好,我是${this.name}, 性别:${this.gender}, 年龄:${this.age}, 我想说:${n}`);}
}
myUser1.run(888);
定义方法结构
interface MyFunction {(a: number, b: number): number;
}const sumResult: MyFunction = (a, b) => a + b;
console.log(sumResult(1, 2));
接口之间的继承
interface ParentInterface {name: stringage: number
}
interface ChildInterface extends ParentInterface {grade: number
}
const john: ChildInterface = {name: "john",age: 30,grade: 10
}
console.log(`姓名:${john.name}年龄:${john.age}分数:${john.grade}`
);
接口合并
interface PersonInterface {name: stringage: number
}
interface PersonInterface {//声明方法run(n: number): void//把方法复制给变量run1: (n: number) => void
}
class person1 implements PersonInterface {// name:string// age:number// constructor(name:string, age:number){// this.name =name;// this.age = age;// }constructor(public name: string, public age: number) { }run(n: number): void {console.log(`run 方法,参数:${n}`);}// run1: (n: number) => void = (n: number) => {// }run1 = (n: number) => {console.log(`run1 方法,参数:${n}`);}
}
总结:何时使用接口?
- 定义对象的格式:描述数据模型、AP! 响应格式、配置对象!.…等等,是开发中用的最多的场景。
- 类的契约:规定一个类需要实现哪些属性和方法。
- 扩展已有接口:一般用于扩展第三方库的类型, 这种特性在大型项目中可能会用到
一些相似的概念
interface与type的区别
相同点: interface 和 type 都可以用于定义对象结构,在定义对象结构时两者可以互换。
不同点:
- interface:更专注于定义对象和类的结构,支持继承、合并
- type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并。
定义对象结构
interface Obj1 {name: stringage: numberrun(s: string): void
}
//使用
let obj1: Obj1 = {name: "zhangsan",age: 10,run(s: string): void {console.log(`this is run method, param: ${s}`);}
}type Obj2 = {name: stringage: numberrun: (s: string) => void
}
//使用
const obj2: Obj2 = {name: "lisi",age: 10,run: (s: string): void => {console.log(`这是lisi, 编号:${s}`);}
}
交叉
//类型交叉
type Cross1 = {name: stringage: number
} & {run: (s: string) => void
}
type Cross2 = Cross1 & {sayHi(): void
}
const student: Cross2 = {name: "stu",age: 10,run(s) {console.log("this is run method!", s);},sayHi() {console.log("say hi");}
}
console.log(student.name, student.age);
student.run("abc");
student.sayHi();//接口交叉
interface Cross3 {name: stringage: numberrun: (s: string) => void
}
interface Cross3 {sayHi(): void
}
interface Cross4 extends Cross3 {grade: number
}
const stu1: Cross4 = {name: "stu123",age: 10,grade: 30,run(s) {console.log(`this is run method ,param:${s}`);},sayHi() {console.log("this is say hi method!");},
}
console.log(stu1.age, stu1.name, stu1.grade);
stu1.sayHi();
stu1.run("abc");
interface与抽象类的区别
相同点:
都能定义一个类的格式(定义类应遵循的契约)
不相同:
- 接口:只能描述结构,不能有任何实现代码,一个类可以实现多个接口。
- 抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类
一个类可以实现多个接口
interface FlyInterface {fly(): void
}
interface SwimInterface {swim(): void
}
class Duck implements FlyInterface, SwimInterface {fly(): void {console.log("the duck can fly!");}swim(): void {console.log("the duck can swim!");}
}
泛型
泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。
泛型函数
function getData1<T, U>(data1: T, data2: U): T {console.log(`data1:${data1}`);console.log(`data2:${data2}`);return data1;
}
getData1<string, string>("abc", 'def');
泛型接口
interface Fx<T> {name: stringage: numberother: T
}
let f1: Fx<string>;
let f2: Fx<number>;
f1 = {name: "zs",age: 10,other: "abc"
}f2 = {name: "ls",age: 20,other: 123
}
泛型约束
interface LengthInterface {length: number
}
//传入的值必须要有length属性
function getLength<T extends LengthInterface>(data: T): void {console.log(data.length);
}
// getLength<number>(100);
getLength<string>('123');
getLength<Array<string>>(["1","2"]);
泛型类
class Queue<T> {constructor(public name: string,public age: number,public data: T) { }run() {console.log(this.name, this.age);console.log(this.data);}
}
let queue = new Queue<number>('zs', 10, 100);
queue.run();
类型声明文件
类型声明文件是 TypeScript 中的一种特殊文件,通常以.d.ts 作为扩展名。它的主要作用是为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行类型检查和提示。