TypeScript(尚硅谷学习笔记)
一、概念
-
什么是 TS?
- 是添加了类型系统的 JS,适用于任何规模的项目
- 是一门静态类型、弱类型的语言
- 是完全兼容 JS 的,它不会修改 JS 运行时的特性
-
TS 特点:
- TS 需要先编译为 JS,然后运行在浏览器、Node.js 等任何能运行 JS 的环境中
- 拥有很多编译选项,可自由决定类型检查的严格程度
- 可以和 JS 共存
- 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力
- 拥有活跃的社区,大多数常用的第三方库都提供了类型声明
- 与标准同步发展,符合最新的 ES 标准
-
核心设计理念:在完整保留 JS 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug
-
TS 与 JS 的不同点:
- TS 是静态类型,它在运行前需要先编译为 JS,而在编译阶段就会进行类型检查;TS 坚持与 ES 标准同步发展;TS 非常适用于大型项目
- JS 是动态类型,它在运行时才会进行类型检查
-
TS 与 JS 的相同点:
- 它们都是弱类型,允许隐式类型转换
基础内容
- 类型声明 使用 : 声明变量的类型,: 的前后有没有空格都可以
// 给变量指定类型
let a : number = 10;
// 给函数的参数指定类型
function sum(a : number, b : number){
return a + b;
}
// 给函数的返回值指定类型
function sum(a: number, b: number): number{
return a + b;
}
let result = sum(123, 456);
如果变量的声明和赋值是同时进行的,ts 可以自动对变量进行类型检测 即使指定不符合的变量类型,执行 ts 文件时依然会生成 js 文件;如果要在报错的时候终止 js 文件的生成,可以在 tsconfig.json 中配置 noEmitOnError 即可
- 数据类型
- number
表示数值类型
let a: number = 19; - string
表示字符串类型
1.
let a: string = "abc"; - Boolean
表示布尔类型
let a: boolean = true;
- 注意,使用构造函数 Boolean 创造的对象不是布尔值,其返回的是一个 Boolean 对象
let createdByNewBoolean: Boolean = new Boolean(1);
- 联合类型表示取值可以为多种类型中的一种
当使用字面量进行类型声明时,是无法修改其值的
let a: 10;
//可以使用 | 来连接多个类型
let a: "male" | "female";
let a: string | number;
//当使用联合类型时,只能访问此联合类型的所有类型里共有的属性或方法
-
any 表示任意类型,当一个变量设置为 any 相当于关闭了对该变量的 ts 类型检测 声明变量如果不指定类型,则 ts 解析器会自动判断变量的类型为 any(隐式any) 当把一个 any 类型的值赋值给其他变量时不会报错,不建议使用
-
unknown 表示未知类型的值,是类型安全的 any 当把一个 unknown 类型的值赋值给其他变量时会报错,建议使用
解决报错1:进行类型判断
let a: unknown;
a = "hello";
let s: string = "world";
if(typeof a === "string"){
s = a;
}
解决报错2:类型断言
let a: unknown;
a = "hello";
let s: string = "world";
s = a as string;
s = <string>a;
- void 表示空,在函数中表示没有返回值
function fn(): void{xxx}
声明 void 类型的变量只能赋值给 undefined 和 null,而不能赋值给 number 类型
- undefined & null 可以使用其来定义这两个原始数据类型 undefined 和 null 是所有类型的子类型,所以其可以赋值给 number 类型的变量
let u: number = undefined;
- never 表示永远不会返回结果,用于进行报错
function fn2(): never{
throw new Error("error!");
}
- object 表示对象,使用 {} 指定对象中可以包含哪些属性 属性名后加 ? 表示属性是可选的
let b: {name: string, age?: number};
b = {name: "孙悟空"};
//使用 [propName: string]: any 表示任意类型的属性
let c: {name: string, [propName: string]: any};
c = {name: "孙悟空", age: 18}
- function 使用箭头函数来设置函数的类型声明
let d: (a: number, b: number) => number;
d = function(a, b){
return a + b;
}
- array 表示数组,数组表示法主要有以下几个:
- 类型+方括号:
let e: string[];
e = ["a", "b"];
- 数组泛型
let f: Array<number>;
f = [1, 2];
- 接口:不常用,主要用来表示类数组
// 只要索引的类型是数字时,那么值的类型必须是数字
interface NumberArray{
[index: number]: number
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5]
- tuple 表示元组,元组就是固定长度的数组
let t: [string, string];
t = ["a", "b"];
- enum表示枚举
enum Gender{
male = 0,
female = 1
}
let h: {
name: string,
gender: Gender
}
h = {
name: "孙悟空",
gender: Gender.male
}
- 类型推论 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型 例如:
let a = "a";
// 等价于
let a: string = "a";
- 类型别名 当我们需要很多类型时,可能会因为太长而增加书写量,所以可以给类型设置别名
type myType = string | number | boolean;
let a1: myType;
let a2: myType;
let a3: myType;
a1 = "a";
a2 = 1;
a3 = true;
- 编译选项
- 自动编译 可以监视 ts 文件的变化,自动编译,在命令行中执行命令即可 tsc app.ts -w
- 缺点:只可以监视当前文件,且无法关闭监视窗口
继承
- 使用继承后,子类会拥有父类所有的属性和方法;
- 如果希望在子类中添加一些父类没有的东西,直接加就行;
- 如果子类中添加了和父类相同的方法,子类方法会覆盖父类,这种方式称为方法重写。
//继承
(function () {
//把重复代码抽离出来 重新写一个类
class Animal{
name:string;
age:number;
constructor(name:string, age:number) {
this.name = name;
this.age = age;
}
sayhello(){
console.log("动物在叫")
}
}
//继承Animal类 Animal称为父类,Dog为子类,使用继承后 子类会有弗雷所有属性
class Dog extends Animal{
run(){
console.log("狗在跑")
}
}
class Cat extends Animal{
sayhello(){
console.log("喵喵喵喵喵喵")
}
}
const dog = new Dog('wancai', 1);
console.log(dog);
dog.run();
const cat = new Cat('咪咪', 1);
console.log(cat);
cat.sayhello();
})();
注意:如果在子类中写了构造函数,则必须对父类的构造函数进行调用,即使用 super()。
class Cat extends Animal{
age:number;
//如果在子类中写了构造函数,则必须对父类的构造函数进行调用,即使用 super()
constructor(name:string,age:number){
super(name,age)
this.age = age+2
}
sayhello() {
//
super.sayhello();//super表示当前类的父类
}
}
抽象类:abstract class Animal,以abstract开头的类抽象类,只能被用来继承,不能用来创建实例,即不能new Animal。 抽象方法只能定义在抽象类中,而且必须是没有方法体。子类必须对抽象方法进行重写。
//父类
abstract class Animal{
name:string;
age:number;
constructor(name:string, age:number) {
this.name = name;
this.age = age;
}
//抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
abstract sayhello():void
}
//子类
class Cat extends Animal{
sayhello() {
console.log('喵喵喵喵喵')
}
}
接口
接口就是用来定义一个类的结构,定义包含哪些属性和方法 和定义myType不同的是,myinterface是可以重复定义的,可以限制类的结构。接口中的所有属性和方法都不能有实际值 只定义结构,不考虑实际值 ,都是抽象方法。
//描述一个对象的类型
type myType = {
name:string;
age:number
}
const obj1:myType = {
name:'xixi',
age:19
}
// 和定义myType不同的是,myinterface是可以重复定义的,定义的结果就是两者的合并,可以限制类的结构, 用接口表示 接口就是用来定义一个类的结构,定义包含哪些属性和方法
//接口中的所有属性和方法都不能有实际值 只定义结构,不考虑实际值 都是抽象方法
interface myInterface{
name:string;
age:number;
}
interface myInterface{
gender:string,
sayhello():void
}
const obj2:myInterface = {
name:'xixi',
age:19,
gender:'male',
sayhello() {
console.log('hello')
}
}
// 类实现接口
interface myInterface{
name:string;
sayhello():void
}
class Myclass implements myInterface{
name:string;
//构造函数用来进行初始化
constructor(name:string) {
this.name = name
}
sayhello() {
console.log('hello')
}
}
属性封装
- 类的修饰符:
-
public:公有属性,可以在任意位置访问与修改 -
private:私有属性,只能在类内部进行访问修改 -
protected:受保护的属性,只能在当前类和当前类的子类中使用 -
类中定义的属性与方法,属性可以任意被修改会导致对象中的数据变得非常不安全。
-
getter方法用来读取属性,setter方法设置属性,它们被称为属性的存储器。
class Person{
private name: string;
private age: number;
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
//定义方法,用来获取name属性。
getName(){
return this.name;
}
//定义方法,设置name属性
setName(value:string){
this.name = value;
}
}
const p = new Person("孙悟空", 18);
p.setName("猪八戒");
console.log(p.getName());
- 在 ts 中我们需要设置属性的修饰符为
private - ts 中简化了操作,设置 getter 与 setter 的方法如下:
class Person{
private _name: string;
private _age: number;
constructor(name: string, age: number){
this._name = name;
this._age = age;
}
get name(){
return this._name;
}
set name (value:string){
this._name = value;
}
}
const p = new Person("孙悟空", 18);
p.name = 'xixi'
console.log(p);
console.log(p.name);
泛型
//这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。
function fn<T>(arg: T): T{
return arg;
}
//方式一(直接使用):
fn(10)
//使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
//方式二(指定类型):
fn<number>(10)
//也可以在函数后手动指定泛型
//可以同时指定多个泛型,泛型间使用逗号隔开:
function fn1<T, K>(a: T, b: K): K{
return b;
}
fn1<number, string>(10, "hello");
//使用泛型时,完全可以将泛型当成是一个普通的类去使用
//类中同样可以使用泛型:
class MyClass<T>{
name: T;
constructor(prop: T){
this.name = prop;
}
}
//除此之外,也可以对泛型的范围进行约束
interface MyInter{
length: number;
name:string
}
//表示泛型T必须是Inter子类
function fn3<T extends MyInter>(arg: T): number{
return arg.length;
}
fn3({length:12,name:''})
const ma = new MyClass<string>('xiix')
//使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。