TypeScript是JavaScript的超集。
它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用。
相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。
下载Node.js
安装Node.js
使用npm全局安装typescript
创建一个ts文件
使用tsc对ts文件进行编译
进入命令行
进入ts文件所在目录
执行命令:tsc xxx.ts
自动类型判断
类型 | 例子 | 描述 |
---|---|---|
number | 1, -33, 2.5 | 任意数字 |
string | ‘hi’, “hi”, hi | 任意字符串 |
boolean | true、false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值 |
any | * | 任意类型 |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {name:‘孙悟空’} | 任意的JS对象 |
array | [1,2,3] | 任意JS数组 |
tuple | [4,5] | 元素,TS新增类型,固定长度数组 |
enum | enum{A, B} | 枚举,TS中新增类型 |
// 字符串类型变量 类似对象图 对象名:类名
let a: string
a= = "10"
// 数字类型变量
let b: number;
// 严格类型要求,但类型错误仅在 TypeScript 编译时出现 但是运行转换为js任然可以实现
a = "20";
b = 30;
// 类型变换为字符串
a = "我是帅哥";
a = a + "" + b;
let c: boolean = false; //申明变量的时候直接赋值
或者
let cc=true //效果一样的
let ss: Function = () => {
console.log("ss");
};
// 函数声明,指定参数类型和返回值类型
let sum: Function = (a: number, b: number): number => {
return a + b;
};
// 函数声明(另一种形式)
function divide(a: number, b: number): number {
return a - b;
}
类似常量,无法二次赋值
let aa: 10;
aa = 20;//这里编译器报错
// 字面量常用于限制变量值
let name1: "jack" | "john" | "mike" = "jack";
console.log(name1);
name1 = "john";
name1 = "mike"; // 只能赋值三个中的一个
//或者使用非空断言 确定声明的对象赋值后不为空
let a!: number;
let b!: number;
//并且申明和初始化,不能同时进行
a = 16;
b = 13;
console.log(a);
const sum = (a: number, b: number): number => a + b;
console.log("和是" + sum(a, b));
let op: string | boolean = false;
op = "这是字面量的第二种用法,限制类型";
类似 JavaScript 的 var
let d: any;
d = false;
d = 13;
d = "相当于设置为 any 后,关闭了 TypeScript 的类型检测";
// 隐式 any
let d1;
let d2: boolean = true;
// 创建变量不复制即为隐式 any
d2 = 45; // 直接申明赋值默认解析为赋值类型
d1 = 45;
d1 = "asas";
当不知道传递过来的参数是什么,或者没想好变量定义什么时候就可以使用 未知类型
let e: unknown;
e = 10;
e = "hello";
e = {};
// 类型判断和处理
//未知变量和any的区别是,any可以被任何变量接收,其他变量接收后就会变成不受ts 的type监管的原生js变量
//所以一般推荐使用uknow ,未知变量不能直接赋值,需要经过逻辑处理编译器才不会报错
if (typeof e === 'string') {
let e1: string = e as string;
}
// :判断 as (强转) any 简记为不受ts判断的类型 ukonw 类似超累可以接收所有类型,
// 根据typeof 判断类型是否是需要类型 as强转换
/**
* type js 慢慢开始对类型进行限定越来越想java
*/
let s:String=e as String
let bianli: Function = (arr: number[]): void => {
for (let i = 1; i < arr.length; i++) {
console.log(arr[i]);
}
};
函数返回值类型为 boolean 或 number
let sout = (a: number, b: boolean): boolean | number => {
if (a > 3)
return false;
return a;
};
let login = (a: boolean): never => {
throw new Error("code error 代码线程停止 用来抛异常");
};
let obj: object = {
name: String,
age: Number
};
obj = {
name: "123",
age: 15
};
let obj2: Object;
obj2 = {
ahe: 32,
name: "ASAS"
};
obj2 = {
};
推荐使用对象字面量修饰变量
let obj3 :{
asas: string
money: number
};
let obj4: {
name: string,
a?: number
};
obj4 = { name: "name是必须的" };
obj4 = {
name: "name是必须的,加了?的属性表示可选",
a: 888
};
// 可选属性的实际使用
let obj5: { //java的动态变量这样就可以做到任意变量参数 除了对象,还由方法
name: string,
//表示 变量名为字符类型,可变变量值为字符或者数字
[propName: string]: string | number // 或者使用 union 类型包含可能的其他属性类型
};
obj5 = {
name: "name是必须的"
};
// 当使用字面来申明函数时候格式如下,但是很麻烦推荐原来的const 方法名=():返回值=> 接收即可
const methodAdd: (name: string, age: number) => number = (name, age) => {
console.log(name);
return 1;
};
let arr: number[] = [1, 2];
let h: [string, String] = ["1", "2"];
h = ["3", "4"];
// 超出长度会报错
// h = ["3", "4", ""];
enum MaleStatus {
Male,
Female
}
let p: { name: string, gender: MaleStatus } = { name: "hcyh", gender: MaleStatus.Male };
let j: string | number;
let u: { name: string } & { gender: MaleStatus };
u = { name: "hcyh", gender: MaleStatus.Male };
// 不满足交叉类型出错
u = { name: "hcyh", "nv" };
type pre = 1 | 2 | 3 | 4 | 5;
type suffix = 6 | 7 | "你好帅";
方便使用和书写
let l1: pre | suffix;
面向对象是程序中一个非常重要的思想,但是对于java的使用者来说已经很熟悉,所以不在附属
要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象。要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person类来创建人的对象,通过Dog类创建狗的对象,通过Car类来创建汽车的对象,不同的类可以用来创建不同的对象。
定义类(和java一样):
class 类名 {
属性名: 类型;
constructor(参数: 类型){
this.属性名 = 参数;
}
方法名(){
....
}
}
示例:
class Person{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
设置器和访问器,构造函数也是一样的,但是构造器在ts种不允许多个,并且成员的权限和java也是一模一样,一般使用private 私有化变量然后使用设置器和访问器为外界提供读写路径
ts种私有变量一般加_,并且值得注意的是ts对设置器和访问器有转门的get,set关键字
这样的使用设置器只需要
let a=new people("jack",17);
a.name="XXXX"//无需在使用set属性名
类的出现是为了限制对象类型,和字面量申明对象相似,但是这种静态语言(和java高度相似)可以获得很多关系
封装
对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装
默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在TS中可以对属性的权限进行设置
只读属性(readonly):
TS中属性具有三种修饰符:
示例:
public
class Person{
public name: string; // 写或什么都不写都是public
public age: number;
constructor(name: string, age: number){
this.name = name; // 可以在类中修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中可以修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 可以通过对象修改
protected
class Person{
protected name: string;
protected age: number;
constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中可以修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
private
class Person{
private name: string;
private age: number;
constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
class Employee extends Person{
constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中不能修改
}
}
const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改
属性存取器
对于一些不希望被任意修改的属性,可以将其设置为private
直接将其设置为private将导致无法再通过对象修改其中的属性
我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
读取属性的方法叫做setter方法,设置属性的方法叫做getter方法
示例:
class Person{
private _name: string;
constructor(name: string){
this._name = name;
}
get name(){
return this._name;
}
set name(name: string){
this._name = name;
}
}
const p1 = new Person('孙悟空');
console.log(p1.name); // 通过getter读取name属性
p1.name = '猪八戒'; // 通过setter修改name属性
静态属性
静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用
静态属性(方法)使用static开头
示例:
class Tools{
static PI = 3.1415926;
static sum(num1: number, num2: number){
return num1 + num2
}
}
console.log(Tools.PI);
console.log(Tools.sum(123, 456));
this
继承
继承时面向对象中的又一个特性
通过继承可以将其他类中的属性和方法引入到当前类中
示例:
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
bark(){
console.log(`${this.name}在汪汪叫!`);
}
}
const dog = new Dog('旺财', 4);
dog.bark();
通过继承可以在不修改类的情况下完成对类的扩展
重写
发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写,和java一样
示例:
class Animal{
name: string;
age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
run(){
console.log(`父类中的run方法!`);
}
}
class Dog extends Animal{
bark(){
console.log(`${this.name}在汪汪叫!`);
}
run(){
console.log(`子类中的run方法,会重写父类中的run方法!`);
}
}
const dog = new Dog('旺财', 4);
dog.bark();
在子类中可以使用super来完成对父类的引用
抽象类(abstract class)
抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
abstract class Animal{
abstract run(): void;
bark(){
console.log('动物在叫~');
}
}
class Dog extends Animals{
run(){
console.log('狗在跑~');
}
}
使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现
接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
接口ts中可以申明属性和方法,java只能申明属性,但是ts的属性和方法只能在实现类中进行实现
这里使用idea的生成构造器在接口生成失效
在 TypeScript 中,类实例赋值给接口变量的情况有两种可能的解释:结构化类型和类的实例满足接口的要求。
结构化类型
TypeScript 使用结构化类型系统,这意味着如果一个类型具有与另一个类型相同的结构(即相同的属性和方法),它们就是兼容的。在你的例子中,Person 类具有与 PersonInterface 相同的结构,包括 name、age 属性和 sayHello 方法。因此,它们是结构上兼容的。
类的实例满足接口的要求
TypeScript 允许类的实例去满足接口的要求,即使类没有显式使用 implements 关键字声明实现了该接口。如果类的结构与接口相匹配,TypeScript 会认为该类实现了接口,而不需要显式的 implements 关键字。
无需关键字implements 也是实现 关系
ts中有很多隐式申明,比如如果不写方法参数类型和返回值默认就是any(js变量)L,比如直接赋值计算结果,ts自动检查类型,这里接口也是一样的是满足条件的隐式申明,虽然好用,但是失去了ts的初衷(为了高可读)所以开发中想写java一样写就行了
定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。
举个例子:
function test(arg: any): any{
return arg;
}
上例中,test函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型
使用泛型:
function test<T>(arg: T): T{
return arg;
}
这里的<T>
就是泛型,也和java的使用一模一样
那么如何使用上边的函数呢?
方式一(直接使用):
test(10)
使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
方式二(指定类型):
test<number>(10)
也可以在函数后手动指定泛型
可以同时指定多个泛型,泛型间使用逗号隔开:
function test<T, K>(a: T, b: K): K{
return b;
}
test<number, string>(10, "hello");
使用泛型时,完全可以将泛型当成是一个普通的类去使用
类中同样可以使用泛型:
class MyClass<T>{
prop: T;
constructor(prop: T){
this.prop = prop;
}
}
除此之外,也可以对泛型的范围进行约束
interface MyInter{
length: number;
}
function test<T extends MyInter>(arg: T): number{
return arg.length;
}
r