TypeScript
参考:最全的TypeScript学习指南 - 掘金 (juejin.cn)
JavaScript:弱类型动态类型语言
TyoeScript:强类型静态类型语言
类型 | |
---|---|
弱类型 | 类型可以被忽略 |
强类型 | 强制类型定义 |
动态类型 | 执行期间才去发现数据类型 |
静态类型 | 编译时数据类型是固定 |
一,TS类型
1.布尔类型
const flag:boolean = true;
2.Number类型
const number:number = 1;
3.String类型
const str:string = 'hello';
4.Enum类型
enum Color{
RED,
PINK,
BLUE,
}
const pink:Color = Color.PINK
5.数组类型(Array)
const arr:number[] = [1,2,3];
const arr:Array<number> = [1,2,3];
6.元组类型(Tuple)
const tup:[number,string] = [1,'hello']
7.Symbol
我们在使用 Symbol 的时候,必须添加 es6 的编译辅助库 需要在 tsconfig.json 的 libs
字段加上ES2015
Symbol 的值是唯一不变的
const sym1 = Symbol("hello");
const sym2 = Symbol("hello");
console.log(Symbol("hello") === Symbol("hello"));
8.任意类型(any)
任何类型都可以被归为 any
类型 这让 any
类型成为了类型系统的 顶级类型 (也被称作 全局超级类型) TypeScript 允许我们对 any
类型的值执行任何操作 而无需事先执行任何形式的检查
一般使用场景: 第三方库没有提供类型文件时可以使用 any
类型转换遇到困难或者数据结构太复杂难以定义 不过不要太依赖 any
否则就失去了 ts 的意义了
const aL:any = document.getElementById('root')
9.null和undefined
let u:undefined = undefined;
let n:null = null;
10.Unknown 类型
unknow
和any
的主要区别:unknown
类型会更加严格,可以赋任意值,但是不能被赋值给其他类型;any
反之更随意,都可以被分配和分配
let value:unkonwn
11.void类型
void
表示没有任何类型 当一个函数没有返回值时 TS 会认为它的返回值是 void
类型
function hello(name:string):void()
12.never类型
never
一般表示用户无法达到的类型 例如never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
function neverReach(): never {
throw new Error("an error");
}
[^思考:never 和 void 的区别]: void 可以被赋值为 null 和 undefined 的类型。 never 则是一个不包含值的类型。 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。
13.BigInt大数类型
使用BigInt可以安全的存储和操作大整数
let bigNumber:bigint = '12345678990'
14.object,Object和{}类型
object
类型用于表示非原始类型
let objectCase: object;
objectCase = 1; // error
objectCase = "a"; // error
objectCase = true; // error
objectCase = null; // error
objectCase = undefined; // error
objectCase = {}; // ok
Object
代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null
和 undefined
不可以)
{} 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合
15.类型推论
编程语言中能够自动推导出值的类型的能力,他是强静态类型语言中出现的特性,定义时未赋值就会推论成any
类型
二,TS高级类型
1.联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种 未赋值时联合类型上只能访问两个类型共有的属性和方法
let padding: string | number
2.交叉类型
交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type Flag1 = { x: number };
type Flag2 = Flag1 & { y: string };
let flag3: Flag2 = {
x: 1,
y: "hello",
henb,
};
3.类型断言
3.1 类型断言两种方式
let someValue:any = 'this is a string';
//1.尖括号语法
let strLength:number = (<string>someValue).length;
//2.as语法
let strLength:number = (someVlue as string).length
3.2 非空断言
在上下文中当类型检查器无法断定类型时 一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null
和非 undefined
类型
let flag:null | undefined | string
flag!.toString()
3.3 类型断言用法
将一个联合类型断言为其中一个类型
将一个父类断言为更加具体的子类
将任何一个类型断言为any
将any断言为一个具体类型
4.类型保护
类型保护就是一些表达式,编译的时候就能通过类型信息确保某个作用域内变量的类型,其主要思想就是尝试检测属性,方法或原型
4.1 typeof
function double(input: string | number | boolean) {
if (typeof input === "string") {
return input + input;
} else {
if (typeof input === "number") {
return input * 2;
} else {
return !input;
}
}
}
4.2 in关键字
interface Bird {
fly: number;
}
interface Dog {
leg: number;
}
function getNumber(value: Bird | Dog) {
if ("fly" in value) {
return value.fly;
}
return value.leg;
}
4.3 instanceof
class Animal {
name!: string;
}
class Bird extends Animal {
fly!: number;
}
function getName(animal: Animal) {
if (animal instanceof Bird) {
console.log(animal.fly);
} else {
console.log(animal.name);
}
}
4.4 自定义类型保护
通过type is xxx
这样的类型谓词来进行类型保护
function isObject(value: unknown): value is object {
return typeof value === "object" && value !== null;
}
function fn(x: string | object) {
if (isObject(x)) {
// ....
} else {
// .....
}
}
5.类型别名
类型别名会给一个类型起个新名字。类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
type c = number | string | boolean
let a :c= 10
三,函数
1.函数定义
function hello (name:string):void{
console.log('hello',name);
}
2.函数表达式
type SumFunc = (x:number,y:number) =>number;
let countNumber:SumFunc = function(a,b){
return a+b;
}
3.可选参数
TS中函数的形参和实参必须一样,不一样就要配置可选参数,而且必须是最后一个参数
function print(name:string,age?:number):void{
console.log(name,age)
}
print('hahaha')
4.默认参数
function ajax(url:string,method:string = "GET"){
console.log(url,method)
}
5.剩余参数
function sum(...numbers:number[]){
return numbers.reduce((val,item)=>(val+item),0)
}
6.函数重载
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。 在 TypeScript 中,表现为给同一个函数提供多个函数类型定义
function attr(value:string);
function attr(value:number);
function attr(value:string){
console.log(value)
};
7.this
JavaScript里,this的值在函数被调用的时候才会指定
箭头函数能保存函数创建时的 this值
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
//this:Deck 规定this是在Deck对象上调用的,this时Deck类型,而非any
createCardPicker: function(this: Deck) {
return ()=> {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
四,类
1.类的定义
class Person{
name!:string //如果初始属性没赋值就需要加上!
constructor(_name:string){
this.name=_name1-
}
}
2.get和set访问器
class User{
myname:String;
constructor(myname:string){
this,myname
}
get name(){
return this.myname;
}
set name(value){
this,myname = value;
}
}
3.修饰符
修饰符 | |
---|---|
public | 类的公共属性,类的内部,外部,子类均可访问 |
private | 类的私有属性,类内部访问 |
protected | 类的保护属性,类内部,子类可访问 |
static | 类的静态属性,存在于类本身而不是类的实例上 |
readonly | 只读属性 |
abstract | 定义抽象类和在抽象类内部定义抽象方法,做为其它派生类的基类使用,一般不会直接被实例化 |
? | 可选修饰符 |
4.继承
class Student extends Person{
public readonly job:string
constructor(name:string,age:number,sex:string){
super(name,age,sex)
this.job='it'
}
}
5.抽象类
抽象类,无法被实例化,只能被继承并且无法创建抽象类的实例,子类可以对抽象类进行不同实现
抽象方法只能出现抽象类中并且抽象方法不能再抽象类中被具体实现,只能再抽象类的子类中实现(必须要实现)
我们一般用抽象和抽象方法抽离出事物的共性,以后所有继承的子类必须按照规范去实现自己的具体逻辑,增强代码的可维护性和复用性
abstract class Animal{
name:string;
abstract speak():void
}
Class Cat extend Animal{
speck(){
console.log('喵喵喵')
}
}
五,接口
1.对象
//接口可以用来描述`对象的形状`
interface Speakable {
speak(): void;
readonly lng: string; //readonly表示只读属性 后续不可以更改
name?: string; //?表示可选属性
}
let speakman: Speakable = {
// speak() {}, //少属性会报错
name: "hello",
lng: "en",
age: 111, //多属性也会报错
};
2.行为
//接口可以在面向对象编程中表示为行为的抽象
interface Speakable {
speak(): void;
}
interface Eatable {
eat(): void;
}
//一个类可以实现多个接口
class Person implements Speakable, Eatable {
speak() {
console.log("Person说话");
}
// eat() {} //需要实现的接口包含eat方法 不实现会报错
}
3.定义任意属性
如果我们在定义接口的时候无法预先知道有哪些属性的时候,可以使用 [propName:string]:any
,propName 名字是任意的
interface Person {
id: number;
name: string;
[propName: string]: any;
}
let p1 = {
id: 1,
name: "hello",
age: 10,
};
4.接口继承
interface Speakable {
speak(): void;
}
interface SpeakChinese extends Speakable {
speakChinese(): void;
}
class Person implements SpeakChinese {
speak() {
console.log("Person");
}
speakChinese() {
console.log("speakChinese");
}
}
5.函数类型接口
interface discount {
(price: number): number;
}
let cost: discount = function (price: number): number {
return price * 0.8;
};
6.构造函数的类型接口
class Animal {
constructor(public name: string) {}
}
//不加new是修饰函数的,加new是修饰类的
interface WithNameClass {
new (name: string): Animal;
}
function createAnimal(clazz: WithNameClass, name: string) {
return new clazz(name);
}
let a = createAnimal(Animal, "hello");
console.log(a.name);
六,泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
1.多个类型参数
如果我们需要有多个未知的类型占位 那么我们可以定义任何的字母来表示不同的类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, "seven"]); // ['seven', 7]
2.泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
//error
3 泛型接口
定义接口的时候也可以指定泛型
interface Cart<T> {
list: T[];
}
let cart: Cart<{ name: string; price: number }> = {
list: [{ name: "hello", price: 10 }],
};
console.log(cart.list[0].name, cart.list[0].price);
复制代码
我们定义了接口传入的类型 T 之后返回的对象数组里面 T 就是当时传入的参数类型
4 泛型类
class MyArray<T> {
private list: T[] = [];
add(value: T) {
this.list.push(value);
}
getMax(): T {
let result = this.list[0];
for (let i = 0; i < this.list.length; i++) {
if (this.list[i] > result) {
result = this.list[i];
}
}
return result;
}
}
let arr = new MyArray();
arr.add(1);
arr.add(2);
arr.add(3);
let ret = arr.getMax();
console.log(ret);
复制代码
上诉例子我们实现了一个在数组里面添加数字并且获取最大值的泛型类
5 泛型类型别名
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];
复制代码
6 泛型参数的默认类型
我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
七,实用技巧
1.typeof 关键词
类型保护&推出类型
2.keyof关键词
可以用来取得一个对象接口的所有key值
interface Person {
name:string;
age:number;
gender:'male'|'female';
}
type PersonKey = keyof Person;
function getValueByKey(p:Person,key:PersonKey){
return p[key];
}
let val = getValueByKey({name:'hello'},'name');
3.索引访问操作符
使用[]操作符可以进行索引访问
interface Person {
name: string;
age: number;
}
type x = Person["name"]; // x is string
4.映射类型in
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
//批量把一个接口中的属性都变成可选的
type PartPerson = {
[Key in keyof Person]?: Person[Key];
};
let p1: PartPerson = {};
5.infer关键字
在条件类型语句中,可以用infer声明一个类型变量并且对他进行使用
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
八,TypeScript装饰器
1.类的装饰器
类装饰器在类声明之前声明,用来监视、修改或替换类定义
namespace a {
function addNameEat(name: string){
return function (constructor: Function) {
constructor.prototype.name = name;
constructor.prototype.eat = function () {
console.log("eat");
};
};
}
@addNameEat('hello')
class Person {
name!:string;
eat!:string;
constructor() {}
}
let p:Person = new Person();
p.eat()
}
2.属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入2个参数,第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象,第二个参数是属性的名称
function upperCaset(target:any,propertyKey:string){
let value = targetp[propertyKey];
const getter = function(){
return value
}
const setter = function (newVal: string) {
value = newVal.toUpperCase();
};
if (delete target[propertyKey]) {
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
class Person {
@upperCase
name!: string;
}
}
3.方法装饰器
装饰类的方法,接收三个参数
target:Object - 对于静态成员来说时类的构造函数,对于实例成员是类的原型对象
propertyKey:string|symbol - 方法名
descriptor:TypePropertyDescript - 属性描述符
//修饰实例方法
function noEnumerable(
target: any,
property: string,
descriptor: PropertyDescriptor
) {
console.log("target.getName", target.getName);
console.log("target.getAge", target.getAge);
descriptor.enumerable = false;
}
//重写方法
function toNumber(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
let oldMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
args = args.map((item) => parseFloat(item));
return oldMethod.apply(this, args);
};
}
class Person {
name: string = "hello";
public static age: number = 10;
constructor() {}
@noEnumerable
getName() {
console.log(this.name);
}
@toNumber
sum(...args: any[]) {
return args.reduce((accu: number, item: number) => accu + item, 0);
}
}
let p: Person = new Person();
for (let attr in p) {
console.log("attr=", attr);
}
p.getName();
console.log(p.sum("1", "2", "3"));
4.参数修饰器
装饰函数参数
function Log(target: Function, key: string, parameterIndex: number) {
let functionLogged = key || target.prototype.constructor.name;
console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
been decorated`);
}
class Greeter {
greeting: string;
constructor(@Log phrase: string) {
this.greeting = phrase;
}
}
5.装饰器执行顺序
有多个参数装饰器时:从最后一个参数依次向前执行
方法和方法参数中参数装饰器先执行。 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行
类装饰器总是最后执行
function Class1Decorator() {
return function (target: any) {
console.log("类1装饰器");
};
}
function Class2Decorator() {
return function (target: any) {
console.log("类2装饰器");
};
}
function MethodDecorator() {
return function (
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
console.log("方法装饰器");
};
}
function Param1Decorator() {
return function (target: any, methodName: string, paramIndex: number) {
console.log("参数1装饰器");
};
}
function Param2Decorator() {
return function (target: any, methodName: string, paramIndex: number) {
console.log("参数2装饰器");
};
}
function PropertyDecorator(name: string) {
return function (target: any, propertyName: string) {
console.log(name + "属性装饰器");
};
}
@Class1Decorator()
@Class2Decorator()
class Person {
@PropertyDecorator("name")
name: string = "hello";
@PropertyDecorator("age")
age: number = 10;
@MethodDecorator()
greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) {}
}
/**
name属性装饰器
age属性装饰器
参数2装饰器
参数1装饰器
方法装饰器
类2装饰器
类1装饰器
*/
九,模块和声明文件
1.全局模块
默认情况下,当你开始在一个新的TypeScript文件中写下代码时,它处于全局命名空间中
使用全局变量空间是危险的,因为会与文件内的代码命冲突
//foo.ts
const foo = 123
//bar.ts
const bar = foo //allowed
2.文件模块
文件模块也被称为外部模块。typescript文件的跟级别位置含有import或者export,那么它会在这个文件中创建一个本地作用域
模块是TS中外部模块的简称,侧重于代码和复用
模块在其自身的作用域里执行,而不是在全局作用域里
一个模块里的变量,函数,类等在外部是不可见的,除非导出
如果想要使用一个模块里导出的变量则需要导入
//foo.ts
const foo = 123;
export = { foo }
//bar.ts
import { foo } from './foo.ts'
const bar = foo;
十,常见面试题
1.接口和类型别名的区别
1.类型别名可以用于基础数据类型 2.接口可以重复定义(合并)3.接口可以扩展类型别名(extend)类型别名也可以扩展接口(&符号)4.类无法实现定义了联合类型的类型别名
a.基础数据类型与接口不同,类型别名可以用于同其他类型(如基本类型,联合类型,元组)
// primitive
type Name = string;
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
// dom
let div = document.createElement("div");
type B = typeof div;
b.接口可以定义多次(会被自动合并为单个接口)类型别名不可以重复定义
interface Point {
x: number;
}
interface Point {
y: number;
}
const point: Point = { x: 1, y: 2 };
c.接口可以扩展类型别名,类型别名也可以扩展接口,方式不同:接口扩展使用继承(extend继承),类型别名使用交叉类型扩展(&继承)
// 接口扩展接口
interface PointX {
x: number;
}
interface Point extends PointX {
y: number;
}
// ----
// 类型别名扩展类型别名
type PointX = {
x: number;
};
type Point = PointX & {
y: number;
};
// ----
// 接口扩展类型别名
type PointX = {
x: number;
};
interface Point extends PointX {
y: number;
}
// ----
// 类型别名扩展接口
interface PointX {
x: number;
}
type Point = PointX & {
y: number;
};
d.类无法实现定义了联合类型的类型别名
type PartialPoint = { x: number } | { y: number };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint {
// Error
x = 1;
y = 2;
}
**# TypeScript
最全的TypeScript学习指南 - 掘金 (juejin.cn)
一,TS类型
1.布尔类型
const flag:boolean = true;
2.Number类型
const number:number = 1;
3.String类型
const str:string = 'hello';
4.Enum类型
enum Color{
RED,
PINK,
BLUE,
}
const pink:Color = Color.PINK
5.数组类型(Array)
const arr:number[] = [1,2,3];
const arr:Array<number> = [1,2,3];
6.元组类型(Tuple)
const tup:[number,string] = [1,'hello']
7.Symbol
我们在使用 Symbol 的时候,必须添加 es6 的编译辅助库 需要在 tsconfig.json 的 libs
字段加上ES2015
Symbol 的值是唯一不变的
const sym1 = Symbol("hello");
const sym2 = Symbol("hello");
console.log(Symbol("hello") === Symbol("hello"));
8.任意类型(any)
任何类型都可以被归为 any
类型 这让 any
类型成为了类型系统的 顶级类型 (也被称作 全局超级类型) TypeScript 允许我们对 any
类型的值执行任何操作 而无需事先执行任何形式的检查
一般使用场景: 第三方库没有提供类型文件时可以使用 any
类型转换遇到困难或者数据结构太复杂难以定义 不过不要太依赖 any
否则就失去了 ts 的意义了
const aL:any = document.getElementById('root')
9.null和undefined
let u:undefined = undefined;
let n:null = null;
10.Unknown 类型
unknow
和any
的主要区别:unknown
类型会更加严格,可以赋任意值,但是不能被赋值给其他类型;any
反之更随意,都可以被分配和分配
let value:unkonwn
11.void类型
void
表示没有任何类型 当一个函数没有返回值时 TS 会认为它的返回值是 void
类型
function hello(name:string):void()
12.never类型
never
一般表示用户无法达到的类型 例如never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
function neverReach(): never {
throw new Error("an error");
}
[^思考:never 和 void 的区别]: void 可以被赋值为 null 和 undefined 的类型。 never 则是一个不包含值的类型。 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。
13.BigInt大数类型
使用BigInt可以安全的存储和操作大整数
let bigNumber:bigint = '12345678990'
14.object,Object和{}类型
object
类型用于表示非原始类型
let objectCase: object;
objectCase = 1; // error
objectCase = "a"; // error
objectCase = true; // error
objectCase = null; // error
objectCase = undefined; // error
objectCase = {}; // ok
Object
代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null
和 undefined
不可以)
{} 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合
15.类型推论
编程语言中能够自动推导出值的类型的能力,他是强静态类型语言中出现的特性,定义时未赋值就会推论成any
类型
二,TS高级类型
1.联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种 未赋值时联合类型上只能访问两个类型共有的属性和方法
let padding: string | number
2.交叉类型
交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type Flag1 = { x: number };
type Flag2 = Flag1 & { y: string };
let flag3: Flag2 = {
x: 1,
y: "hello",
henb,
};
3.类型断言
3.1 类型断言两种方式
let someValue:any = 'this is a string';
//1.尖括号语法
let strLength:number = (<string>someValue).length;
//2.as语法
let strLength:number = (someVlue as string).length
3.2 非空断言
在上下文中当类型检查器无法断定类型时 一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null
和非 undefined
类型
let flag:null | undefined | string
flag!.toString()
3.3 类型断言用法
将一个联合类型断言为其中一个类型
将一个父类断言为更加具体的子类
将任何一个类型断言为any
将any断言为一个具体类型
4.类型保护
类型保护就是一些表达式,编译的时候就能通过类型信息确保某个作用域内变量的类型,其主要思想就是尝试检测属性,方法或原型
4.1 typeof
function double(input: string | number | boolean) {
if (typeof input === "string") {
return input + input;
} else {
if (typeof input === "number") {
return input * 2;
} else {
return !input;
}
}
}
4.2 in关键字
interface Bird {
fly: number;
}
interface Dog {
leg: number;
}
function getNumber(value: Bird | Dog) {
if ("fly" in value) {
return value.fly;
}
return value.leg;
}
4.3 instanceof
class Animal {
name!: string;
}
class Bird extends Animal {
fly!: number;
}
function getName(animal: Animal) {
if (animal instanceof Bird) {
console.log(animal.fly);
} else {
console.log(animal.name);
}
}
4.4 自定义类型保护
通过type is xxx
这样的类型谓词来进行类型保护
function isObject(value: unknown): value is object {
return typeof value === "object" && value !== null;
}
function fn(x: string | object) {
if (isObject(x)) {
// ....
} else {
// .....
}
}
5.类型别名
类型别名会给一个类型起个新名字。类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
type c = number | string | boolean
let a :c= 10
三,函数
1.函数定义
function hello (name:string):void{
console.log('hello',name);
}
2.函数表达式
type SumFunc = (x:number,y:number) =>number;
let countNumber:SumFunc = function(a,b){
return a+b;
}
3.可选参数
TS中函数的形参和实参必须一样,不一样就要配置可选参数,而且必须是最后一个参数
function print(name:string,age?:number):void{
console.log(name,age)
}
print('hahaha')
4.默认参数
function ajax(url:string,method:string = "GET"){
console.log(url,method)
}
5.剩余参数
function sum(...numbers:number[]){
return numbers.reduce((val,item)=>(val+item),0)
}
6.函数重载
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。 在 TypeScript 中,表现为给同一个函数提供多个函数类型定义
function attr(value:string);
function attr(value:number);
function attr(value:string){
console.log(value)
};
7.this
JavaScript里,this的值在函数被调用的时候才会指定
箭头函数能保存函数创建时的 this值
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
//this:Deck 规定this是在Deck对象上调用的,this时Deck类型,而非any
createCardPicker: function(this: Deck) {
return ()=> {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
四,类
1.类的定义
class Person{
name!:string //如果初始属性没赋值就需要加上!
constructor(_name:string){
this.name=_name1-
}
}
2.get和set访问器
class User{
myname:String;
constructor(myname:string){
this,myname
}
get name(){
return this.myname;
}
set name(value){
this,myname = value;
}
}
3.修饰符
修饰符 | |
---|---|
public | 类的公共属性,类的内部,外部,子类均可访问 |
private | 类的私有属性,类内部访问 |
protected | 类的保护属性,类内部,子类可访问 |
static | 类的静态属性,存在于类本身而不是类的实例上 |
readonly | 只读属性 |
abstract | 定义抽象类和在抽象类内部定义抽象方法,做为其它派生类的基类使用,一般不会直接被实例化 |
? | 可选修饰符 |
4.继承
class Student extends Person{
public readonly job:string
constructor(name:string,age:number,sex:string){
super(name,age,sex)
this.job='it'
}
}
5.抽象类
抽象类,无法被实例化,只能被继承并且无法创建抽象类的实例,子类可以对抽象类进行不同实现
抽象方法只能出现抽象类中并且抽象方法不能再抽象类中被具体实现,只能再抽象类的子类中实现(必须要实现)
我们一般用抽象和抽象方法抽离出事物的共性,以后所有继承的子类必须按照规范去实现自己的具体逻辑,增强代码的可维护性和复用性
abstract class Animal{
name:string;
abstract speak():void
}
Class Cat extend Animal{
speck(){
console.log('喵喵喵')
}
}
五,接口
1.对象
//接口可以用来描述`对象的形状`
interface Speakable {
speak(): void;
readonly lng: string; //readonly表示只读属性 后续不可以更改
name?: string; //?表示可选属性
}
let speakman: Speakable = {
// speak() {}, //少属性会报错
name: "hello",
lng: "en",
age: 111, //多属性也会报错
};
2.行为
//接口可以在面向对象编程中表示为行为的抽象
interface Speakable {
speak(): void;
}
interface Eatable {
eat(): void;
}
//一个类可以实现多个接口
class Person implements Speakable, Eatable {
speak() {
console.log("Person说话");
}
// eat() {} //需要实现的接口包含eat方法 不实现会报错
}
3.定义任意属性
如果我们在定义接口的时候无法预先知道有哪些属性的时候,可以使用 [propName:string]:any
,propName 名字是任意的
interface Person {
id: number;
name: string;
[propName: string]: any;
}
let p1 = {
id: 1,
name: "hello",
age: 10,
};
4.接口继承
interface Speakable {
speak(): void;
}
interface SpeakChinese extends Speakable {
speakChinese(): void;
}
class Person implements SpeakChinese {
speak() {
console.log("Person");
}
speakChinese() {
console.log("speakChinese");
}
}
5.函数类型接口
interface discount {
(price: number): number;
}
let cost: discount = function (price: number): number {
return price * 0.8;
};
6.构造函数的类型接口
class Animal {
constructor(public name: string) {}
}
//不加new是修饰函数的,加new是修饰类的
interface WithNameClass {
new (name: string): Animal;
}
function createAnimal(clazz: WithNameClass, name: string) {
return new clazz(name);
}
let a = createAnimal(Animal, "hello");
console.log(a.name);
六,泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
1.多个类型参数
如果我们需要有多个未知的类型占位 那么我们可以定义任何的字母来表示不同的类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, "seven"]); // ['seven', 7]
2.泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
//error
3 泛型接口
定义接口的时候也可以指定泛型
interface Cart<T> {
list: T[];
}
let cart: Cart<{ name: string; price: number }> = {
list: [{ name: "hello", price: 10 }],
};
console.log(cart.list[0].name, cart.list[0].price);
复制代码
我们定义了接口传入的类型 T 之后返回的对象数组里面 T 就是当时传入的参数类型
4 泛型类
class MyArray<T> {
private list: T[] = [];
add(value: T) {
this.list.push(value);
}
getMax(): T {
let result = this.list[0];
for (let i = 0; i < this.list.length; i++) {
if (this.list[i] > result) {
result = this.list[i];
}
}
return result;
}
}
let arr = new MyArray();
arr.add(1);
arr.add(2);
arr.add(3);
let ret = arr.getMax();
console.log(ret);
复制代码
上诉例子我们实现了一个在数组里面添加数字并且获取最大值的泛型类
5 泛型类型别名
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];
复制代码
6 泛型参数的默认类型
我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
七,实用技巧
1.typeof 关键词
类型保护&推出类型
2.keyof关键词
可以用来取得一个对象接口的所有key值
interface Person {
name:string;
age:number;
gender:'male'|'female';
}
type PersonKey = keyof Person;
function getValueByKey(p:Person,key:PersonKey){
return p[key];
}
let val = getValueByKey({name:'hello'},'name');
3.索引访问操作符
使用[]操作符可以进行索引访问
interface Person {
name: string;
age: number;
}
type x = Person["name"]; // x is string
4.映射类型in
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
//批量把一个接口中的属性都变成可选的
type PartPerson = {
[Key in keyof Person]?: Person[Key];
};
let p1: PartPerson = {};
5.infer关键字
在条件类型语句中,可以用infer声明一个类型变量并且对他进行使用
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
八,TypeScript装饰器
1.类的装饰器
类装饰器在类声明之前声明,用来监视、修改或替换类定义
namespace a {
function addNameEat(name: string){
return function (constructor: Function) {
constructor.prototype.name = name;
constructor.prototype.eat = function () {
console.log("eat");
};
};
}
@addNameEat('hello')
class Person {
name!:string;
eat!:string;
constructor() {}
}
let p:Person = new Person();
p.eat()
}
2.属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入2个参数,第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象,第二个参数是属性的名称
function upperCaset(target:any,propertyKey:string){
let value = targetp[propertyKey];
const getter = function(){
return value
}
const setter = function (newVal: string) {
value = newVal.toUpperCase();
};
if (delete target[propertyKey]) {
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
class Person {
@upperCase
name!: string;
}
}
3.方法装饰器
装饰类的方法,接收三个参数
target:Object - 对于静态成员来说时类的构造函数,对于实例成员是类的原型对象
propertyKey:string|symbol - 方法名
descriptor:TypePropertyDescript - 属性描述符
//修饰实例方法
function noEnumerable(
target: any,
property: string,
descriptor: PropertyDescriptor
) {
console.log("target.getName", target.getName);
console.log("target.getAge", target.getAge);
descriptor.enumerable = false;
}
//重写方法
function toNumber(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
let oldMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
args = args.map((item) => parseFloat(item));
return oldMethod.apply(this, args);
};
}
class Person {
name: string = "hello";
public static age: number = 10;
constructor() {}
@noEnumerable
getName() {
console.log(this.name);
}
@toNumber
sum(...args: any[]) {
return args.reduce((accu: number, item: number) => accu + item, 0);
}
}
let p: Person = new Person();
for (let attr in p) {
console.log("attr=", attr);
}
p.getName();
console.log(p.sum("1", "2", "3"));
4.参数修饰器
装饰函数参数
function Log(target: Function, key: string, parameterIndex: number) {
let functionLogged = key || target.prototype.constructor.name;
console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
been decorated`);
}
class Greeter {
greeting: string;
constructor(@Log phrase: string) {
this.greeting = phrase;
}
}
5.装饰器执行顺序
有多个参数装饰器时:从最后一个参数依次向前执行
方法和方法参数中参数装饰器先执行。 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行
类装饰器总是最后执行
function Class1Decorator() {
return function (target: any) {
console.log("类1装饰器");
};
}
function Class2Decorator() {
return function (target: any) {
console.log("类2装饰器");
};
}
function MethodDecorator() {
return function (
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
console.log("方法装饰器");
};
}
function Param1Decorator() {
return function (target: any, methodName: string, paramIndex: number) {
console.log("参数1装饰器");
};
}
function Param2Decorator() {
return function (target: any, methodName: string, paramIndex: number) {
console.log("参数2装饰器");
};
}
function PropertyDecorator(name: string) {
return function (target: any, propertyName: string) {
console.log(name + "属性装饰器");
};
}
@Class1Decorator()
@Class2Decorator()
class Person {
@PropertyDecorator("name")
name: string = "hello";
@PropertyDecorator("age")
age: number = 10;
@MethodDecorator()
greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) {}
}
/**
name属性装饰器
age属性装饰器
参数2装饰器
参数1装饰器
方法装饰器
类2装饰器
类1装饰器
*/
九,模块和声明文件
1.全局模块
默认情况下,当你开始在一个新的TypeScript文件中写下代码时,它处于全局命名空间中
使用全局变量空间是危险的,因为会与文件内的代码命冲突
//foo.ts
const foo = 123
//bar.ts
const bar = foo //allowed
2.文件模块
文件模块也被称为外部模块。typescript文件的跟级别位置含有import或者export,那么它会在这个文件中创建一个本地作用域
模块是TS中外部模块的简称,侧重于代码和复用
模块在其自身的作用域里执行,而不是在全局作用域里
一个模块里的变量,函数,类等在外部是不可见的,除非导出
如果想要使用一个模块里导出的变量则需要导入
//foo.ts
const foo = 123;
export = { foo }
//bar.ts
import { foo } from './foo.ts'
const bar = foo;
十,常见面试题
1.接口和类型别名的区别
1.类型别名可以用于基础数据类型 2.接口可以重复定义(合并)3.接口可以扩展类型别名(extend)类型别名也可以扩展接口(&符号)4.类无法实现定义了联合类型的类型别名
a.基础数据类型与接口不同,类型别名可以用于同其他类型(如基本类型,联合类型,元组)
// primitive
type Name = string;
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
// dom
let div = document.createElement("div");
type B = typeof div;
b.接口可以定义多次(会被自动合并为单个接口)类型别名不可以重复定义
interface Point {
x: number;
}
interface Point {
y: number;
}
const point: Point = { x: 1, y: 2 };
c.接口可以扩展类型别名,类型别名也可以扩展接口,方式不同:接口扩展使用继承(extend继承),类型别名使用交叉类型扩展(&继承)
// 接口扩展接口
interface PointX {
x: number;
}
interface Point extends PointX {
y: number;
}
// ----
// 类型别名扩展类型别名
type PointX = {
x: number;
};
type Point = PointX & {
y: number;
};
// ----
// 接口扩展类型别名
type PointX = {
x: number;
};
interface Point extends PointX {
y: number;
}
// ----
// 类型别名扩展接口
interface PointX {
x: number;
}
type Point = PointX & {
y: number;
};
d.类无法实现定义了联合类型的类型别名
type PartialPoint = { x: number } | { y: number };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint {
// Error
x = 1;
y = 2;
}
**