前言
作为一个职场菜鸟,到了公司发现大家已经早就没有用原始的JS了,所有前端代码都是用TypeScript 写的,于是匆匆忙忙学习了TS,这里分享自己的学习笔记,希望能帮助一些和我一样的初学者,么么哒!!!
1、开发环境搭建
(1)采用brew工具下载安装node
注意:最好不要直接官网下载node进行安装,会导致很多卸载残留 brew安装方法: 命令行输入:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
(2)安装node和yarn
这里使用yarn作为工具,作用和npm类似 ,你也可以使用npm
使用brew进行node安装,命令行输入命令:
brew install node
(3)安装TypeScript
命令行输入:
yarn global add typescript
2、编译ts文件
进入文件目录,执行命令:tsc ***.ts,此时该文件夹下生成一个同名的js文件,这个js文件就可以直接在浏览器运行。(自己练习的话可以使用在线编译工具)
3、TS的类型
(1)指定变量类型
声明一个变量并指定它的类型语法: let a : type(type指的就是变量类型)
当一个变量声明了类型之后,就只能给变量赋该类型的值(any和unknown类型比价特殊,后面会介绍),如下:
let a: number;
a = 10;
a = 'str' // 当给a赋值一个字符串时会报错
如果变量声明和赋值是同时进行的(如:let a = 123),则TS可以根据所赋的值自动检测类型,以后修改变量值时也只能修改成一样类型的值。
(2)TS数据类型总结
-
number: 任意数字
let a: number = 1
-
string: 任意字符串
let str: string = 'hello'
-
boolean: 布尔值
let isInteresting: boolean = true
-
any:任意类型
let value: any = 'showee' value = 100 //不报错 let num: boolean = value // 不报错 let obj: any = {} obj.getName() // 不报错
any类型的变量可以获取它的任何属性和方法,当一个变量的类型设置为any后,相当于关闭了该变量的TS的类型检测,因此不建议使用any类型。
如果声明一个变量时不指定类型,则TS解析器会自动判断变量的类型为any,如下所示:
let d; // 不指定类型,只声明了变量
d = 10; // 不报错
d = 'hello'; // 不报错
d = true; // 不报错
- unknown:类型安全的any
表示某个变量的类型是未知的,也意味着这个变量可能是任意类型的值,这个值可能是来自于动态内容。
let value: unknown = 'showee'
value = 100 //不报错
let num: boolean = value // 报错!这里和any的表现不同
let obj: unknown = {
getName: function() {}
}
obj.getName() // 报错!
我们可以通过类型守卫(如:typeof)来收窄变量的类型范围
let value: unknown = 100
let num: number
if (typeof value === 'number') {
num = value // 此时不报错
}
或者通过类型断言来告诉解析器变量的实际类型
let e: unkown;
let s: string;
s = e as string; // 写法一:变量 as 类型
s = <string>e; // 写法二:<类型>变量
-
void:没有值(或undefined),常用来设置没有返回值的函数
function fn(): void{ return; }
-
never:不能是任何值,常用来设置函数不会返回结果
function error(message: string): never { throw new Error(message); // 抛出错误,函数并没有返回值 }
function infiniteLoop(): never { while (true) {} // 函数没有返回值 }
-
- object:任意的JS对象
let obj: object = { name: 'showee' }
// 用{}来制定对象中可以包含哪些属性 let obj1: {name: string, age?: number}; // 可以用?来表示可选属性 obj1 = {name: 'showee'}
let obj2: {name: string, [propName: string]:any}; // [propName: string]:any 表示除了name之外还可以添加任意类型的属性
-
Object:看起来object和Object只有大小写的区别,Object实际上表示的范围更加的广泛,除了表示对象还可以表示数字、字符串、布尔值等基本类型(但不包括Null和Undefined)
const obj: Object = { name: 'showee' } const obj: Object = 'showee' const obj: Object = 100 const obj: Object = true
const obj: {} = { name: 'akara' } // 这种写法{},这和Object是完全等价的
通常表示对象建议使用object而不是Object
- array:任意js数组
// 两种方式
let arr: number[] = [1, 2, 3]
let arr: Array<number> = [1, 2, 3]
-
tuple:元组,TS新增类型,固定长度的数组
let person: [string, number] = ['showee', 3]
-
enum:枚举,TS中新增类型
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true console.log(Days["Mon"] === 1); // true console.log(Days["Tue"] === 2); // true console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true console.log(Days[1] === "Mon"); // true console.log(Days[2] === "Tue"); // true console.log(Days[6] === "Sat"); // true
-
字面量类型:限制变量的值就是该字面量的值
// 字符串字面量类型 let str: 'small' = 'large'
// 数字字面量类型 let num: 1 = 1
// 布尔字面量类型 let boo: true = true
注意,用let和const声明变量时,其类型是不同的,如下:
let name = 'aka' // string
const name = 'aka' // 'aka'
4、函数
(1)参数
可选和默认参数:TS中函数对参数的长度要求很严格,可使用?表示可选参数,也可以使用参数默认值:
function A(name: string,age?:number): string | undefined {
return name;
}
function B(name: string = 'showee'): string {
return name;
}
剩余参数:当需要同时操作多个参数,或者你并不知道会有多少参数传入,此时可以把所有参数收集到一个变量里面
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("xiao", "ke", "ai");
(2)匿名函数
匿名函数中会自动推导参数的类型
let arr = [1, 2, 3]
arr.forEach(item => console.log(item)) // 不需要给item添加类型注解
(3)this
在对象字面量中的this类型可能有多种:
如果这个方法显示指定了this参数,那么this具有该参数的类型
let bar = {
x: 'hello',
f(this: { message: string }) {
this; // { message: string }
}
};
否则,如果方法由带this参数的签名进行上下文键入,那么this具有该参数的类型
let foo = {
x: 'hello',
f(n: number) {
this; // { x: string, f(n: number): void }
}
};
否则,如果 --noImplicitThis 选项已经启用,并且对象字面量中包含由 ThisType 键入的上下文类型,那么 this 的类型为 T
否则,如果--noImplicitThis 选项已经启用,并且对象字面量中不包含由 ThisType 键入的上下文类型,那么 this 的类型为该上下文类型
否则,如果 --noImplicitThis 选项已经启用,this 具有该对象字面量的类型
否则,this 的类型为 any
(4)重载
为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用,下面是来自官网的一个示例
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);
为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
5、类
(1)定义和使用
class Person{
// 定义实例属性
name: string;
age: number;
readonly id: string = 'sfijper'
// 在属性前使用static关键字可以定义类属性(静态属性),直接通过类访问
static gender: string = 'female';
constructor(name: string, age: number) {
this.name = name;
this.age = age
}
// 实例方法,如果在方法名前面加static则成为静态方法,静态方法直接通过类调用
sayHi() {
return `Hi!I am ${this.name},I am ${this.age} years old`
}
}
const xiaohong = new Person('xiahong', 3);
console.log(Person.gender) // 静态属性的访问方法
xiaohong.id = 'sofj' // 报错,只读属性不能修改
console.log(xiaohong.sayHi()) // 输出 "Hi!I am xiahong,I am 3 years old"
(2)继承
使用继承后,子类会拥有父类所有的方法和属性,通过继承的方式可以将多个类中共有的代码写在一个父类中,这样只需要写一次即可让所有子类同时拥有父类中的属性和方法,并且可以在子类中添加一些分类中没有的属性和方法。在子类中添加和父类相同的方法,可以覆盖掉父类中的该方法,这种称为方法重写。
class Person{
// 定义实例属性
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age
}
sayHi() {
console.log(`Hi!I am ${this.name},I am ${this.age} years old`)
}
}
// 使Boy继承Person类
class Boy extends Person{
sing() {
console.log('lalalalala...')
}
// 添加和父类相同的方法,可以覆盖掉父类的
sayHi() {
console.log('I am a cool boy')
}
}
const xiaohong = new Person('xiaohong', 3);
xiaohong.sayHi(); // "Hi!I am xiaohong,I am 3 years old"
const boy = new Boy('xiaoshuai', 3);
boy.sing(); // "lalalalala..."
boy.sayHi(); // "I am a cool boy"
super关键字:在类的方法中,super就表示当前子类的父类,如果需要在子类中写构造函数,则在子类的构造函数中必须使用super()调用父类的构造函数
class Person{
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age
}
sayHi() {
console.log(`Hi!I am ${this.name},I am ${this.age} years old`)
}
}
class Boy extends Person{
id: string
constructor(name: string, age: number, id: string) {
super(name, age);
this.id = id;
}
sayHi() {
super.sayHi();
console.log(`Hi!I am ${this.name},I am ${this.age} years old,my id is ${this.id}`)
}
}
const xiaohong = new Person('xiaohong', 3);
const boy = new Boy('xiaoshuai', 3, 'abcdefg');
xiaohong.sayHi();
boy.sayHi();
(3)抽象类
以abstract开头的类是抽象类,抽象类和其他类区别不大,只是不能用来创建对象,但是可以被继承(通常我们创建的抽象类就是专门用来被继承的,用来作为父类)。
抽象类中可以添加抽象方法,抽象方法使用abstract开头,没有方法体。抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写。
abstract class Person{
name: string;
constructor(name: string) {
this.name = name;
}
abstract sayHi(): void;
}
class Boy extends Person{
// 必须重写抽象方法
sayHi() {
console.log(`I am a boy`)
}
}
const person = new Person(); // 报错 Cannot create an instance of an abstract class.
const boy = new Boy('xiaoshuai');
boy.sayHi();
(4)接口
接口用来定义一个类的结构,它定义了一个类中应该包含哪些属性和方法,同时接口也可以当成类型声明去使用。
interface myInterface{
name: string;
age: number;
}
// 当定义两个同名的接口是=时,相当于合并在一起的效果
interface myInterface{
gender: string;
}
const obj: myInterface = {
name: 'showee',
age: 3,
gender: 'male'
}
接口中的所有属性都不能有实际值,并且所有方法都是抽象方法。
定义类时,可以使类去实现一个接口(实现接口就是指使我们定义的类满足接口的要求)。
interface myInterface{
name: string;
age: number;
say(): void;
}
class myClass implements myInterface{
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say() {
console.log('hello')
}
}
(5)属性的封装
TypeScript 有三种访问修饰符, public、private 和 protected
-
public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
class Animal { public name: string; public constructor (name) { this.name = name; } }
-
private 修饰的属性或方法是私有的,不能在声明它的类的外部访问和修改,我们可以在类中添加方法使其能够被外部访问和修改(即getter和setter,称为属性存取器)
class Person{ private _name: string;
constructor(name: string) { this._name = name; } // 定义方法,获取name属性 get name() { return this._name } // 定义方法,修改name属性 set name(value: string) { this._name = value }
}
const person = new Person('showee'); console.log(person.name); // 'showee' person.name = 'erbao'; console.log(person.name); // 'erbao'
-
protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
class Person{ protected name: string;
constructor(name: string) { this.name = name; }
}
class Boy extends Person{ test() { console.log(this.name) } }
const xiaohong = new Person('xiaohong'); const boy = new Boy('xiaoshuai'); console.log(xiaohong.name) // 报错 Property 'name' is protected and only accessible within class 'Person' and its subclasses. boy.test(); // ‘xiaoshuai’
6、泛型
在定义函数或类时,如果遇到类型不明确的就可以使用泛型,可以同时指定多个
function fn<T>(a: T): T{
return a;
}
// 可以直接调用具有泛型的函数
let res1 = fn(10); // 不指定泛型,TS可以自动度类型进行推断
let res2 = fn<string>('hello'); // 指定泛型
console.log(typeof res1, typeof res2);
// 接口中使用泛型
interface Inter{
length: number;
}
// T extends Inter 表示泛型T必须是Inter实现类(子类)
function fn1<T extends Inter>(a: T): number{
return a.length;
}
// 类中使用泛型
class MyClass{
name: T;
constructor(name: T) {
this.name = name;
}
}
const myClass = new MyClass<string>('showee');
7、类型操作
(1)联合类型
联合类型表示取值可以为多种类型中的一种。
let value: string | number;
value = 'showee';
value = 123;
当不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。
function getLength(something: string | number): number{
return something.length;
}
// 这里会报错,因为length不是string和number的共有属性
function getString(something: string | number): string{
return something.toString();
}
// 这里不会报错,因为toString是string和number的共有属性
(2)交叉类型
type a = {
name: string;
}
type b = {
age: number;
}
let obj: a & b = {
name: 'showee',
age: 0,
}
(3)类型断言
当你非常了解一个值的类型的时候,你可以通过断言的方式告诉编译器你的想法,这有些类似于其他语言中的类型转换,类型断言有以下两种方式
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length; // 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length; // as语法
(4)类型保护(类型守卫)
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。
类型保护与特性检测有些类似,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护
- typeof
在JS中typeof可以用来获取变量的基本类型,而在TS中,可以获取类型
function doSome(x: number | string) {
if (typeof x === 'string') {
// 在这个块中,TypeScript 知道 `x` 的类型必须是 `string`
console.log(x.subtr(1)); // Error: 'subtr' 方法并没有存在于 `string` 上
console.log(x.substr(1)); // ok
}
x.substr(1); // Error: 无法保证 `x` 是 `string` 类型
}
-
in关键字
interface Admin { name: string; privileges: string[]; }
interface Employee { name: string; startDate: Date; }
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) { console.log("Name: " + emp.name); if ("privileges" in emp) { console.log("Privileges: " + emp.privileges); } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate); } }
-
Instanceof
class Foo { foo = 123; common = '123'; }
class Bar { bar = 123; common = '123'; }
function doStuff(arg: Foo | Bar) { if (arg instanceof Foo) { console.log(arg.foo); // ok console.log(arg.bar); // Error } if (arg instanceof Bar) { console.log(arg.foo); // Error console.log(arg.bar); // ok } }
doStuff(new Foo()); doStuff(new Bar());
-
自定义类型保护的类型谓词
function isNumber(x: any): x is number { return typeof x === "number"; }
function isString(x: any): x is string { return typeof x === "string"; }
(5)类型别名
类型别名用来给一个类型取一个新的名字
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
(6)keyof
该操作符可以用于获取某种类型的所有键,其返回类型是联合类型
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string