前言
作为一个职场菜鸟,到了公司发现大家已经早就没有用原始的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 = trueconst 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); // trueconsole.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
复制代码