# TypeScript
# 1. 变量的类型
# 1.1 有哪些类型
强类型语言,JavaScript的超集,
- boolean
- number(没有整型和浮点型的区分)
- string
- array
- tuple(元组,数组的一种)
- enum(枚举类型)
- any(任意类型)
- null && undefined (其他类型的子类型)
- void(常用于定义方法的时候,方法没有返回值)
- never(其他类型,代表从不会出现的值,never类型只能被never类型所赋值)
let isSleepy: boolean = true;
let num: number = 123;
let str: string = "This is why we play.";
# 1.2 数组
// 两种方法定义数组
let arr1: string[] = ["Duncan", "James", "Allen"];
let arr2: Array<number> = [4, 8, 12, 16];
let arr3: Array<any> = [12, "Range"];
# 1.3 元组
// 元组,数组的一种,指定多种类型
let tp: [string, number, boolean] = ["range", 12, false];
# 1.4 枚举类型
// 枚举类型,主要用于标识状态/固定值
enum Flag {success = 1, error = -1}
let f: Flag = Flag.error;
console.log(f) // -1
enum Color {red, blue, orange = 9, green, purple}
let c1: Color = Color.blue; // 默认值,1
let c2: Color = Color.green // 以上一个值为基准,10
# 2. 函数
# 2.1 函数声明
// 函数声明
function run(): string {
return 'run';
}
# 2.2 匿名函数
// 匿名函数
let func = function (): number {
return 2043;
};
# 2.3 定义方法传参
// 定义方法传参
function getInfo(name: string, age?: number): string {
// age? 表示该参数为可选参数,并且可选参数必须放在参数的末尾。
if (!age) {
return `${name} --- age: Secret`;
} else {
return `${name} --- ${age}`;
}
}
let myName: string = 'range';
let myAge: number = 20;
console.log(getInfo(myName, myAge));
# 2.4 默认参数
// 设置默认参数
function getName(name: string = 'range') {
return name;
}
console.log(getName());
# 2.5 剩余参数
// 剩余参数
// 三点运算符接收形参传过来的值
function sum(a: number, ...numbers: number[]): number {
let ans = 0;
numbers.forEach(number => {
ans += number;
});
return ans * a;
}
console.log(sum(2, 3, 4, 2043));
# 2.6 函数重载
- 函数的重载,相同的方法,不同的参数,实现不同的功能
- 为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用
- 为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
function getInfo(name: string): string;
function getInfo(age: number): number;
// 注意这里的 any 并不是重载列表的一部分
function getInfo(str: any): any {
if (typeof str === 'string') {
return 'my name is ' + str;
} else {
return 'my age is ' + str;
}
}
console.log(getInfo('range'));
# 3. 类
# 3.1 类的创建
// 类的创建
class Person {
// 字段
name: string;
// 构造函数
constructor(name: string) {
this.name = name;
}
// 方法
getName():string {
return this.name
}
setName(name:string):void {
this.name = name;
}
}
let p = new Person('range');
console.log(p.getName());
p.setName("sirius");
console.log(p.getName())
# 3.2 类的继承
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name} is running.`;
}
}
class Boy extends Person {
sex: string;
constructor(name: string) {
// super表示调用父类的构造函数
super(name);
this.sex = 'male';
}
boyRun(): string {
return `A boy named ${this.name} is running.`;
}
}
let b = new Boy('duncan');
console.log(b.run());
console.log(b.boyRun());
# 3.3 类的修饰符
public
:公有,类的内外、子类都可以访问protected
:保护,类和子类可以访问,外部无法访问private
:私有,只有类的内部可以访问- 如果属性不加修饰符,则默认公有
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
run(): string {
return `${this.name} is running.`;
}
}
class Boy extends Person {
public sex: string;
constructor(name: string) {
super(name);
this.sex = 'male';
}
boyRun(): string {
return `The boy named ${this.name} is running.`;
}
}
let boy = new Boy('range');
console.log(boy.boyRun());
# 3.4 静态属性和静态方法
// ES5 中的静态属性和静态方法
function Person(name) {
this.name = name;
}
Person.age = 2043; // 静态属性
Person.getAge = function () { // 静态方法
return this.age;
}
console.log(`Person.age: ${Person.age}`)
console.log(`Person.getAge(): ${Person.getAge()}`)
// TypeScript中的静态属性和静态方法
class Person {
public name: string;
// 静态属性
static age: number = 22;
static hobby: string;
constructor(name: string) {
this.name = name;
}
yield() {
console.log('HHHHHi!');
}
// 静态方法
static printAge() {
// console.log(this.name); // 报错,静态方法中只能调用静态属性
console.log(this.age);
}
}
console.log(Person.age)
Person.printAge();
# 3.6 多态
定义:多态是指,父类定义一个方法不去实现,让继承它的子类去实现,每一个子类有不同的表现。多态属于继承,是继承的一种表现。
// 多态
class Animal {
public name: string;
constructor(name: string) {
this.name = name;
}
eat() {
// 让子类去实现具体的操作,并且不同子类,操作不同
}
}
class Cat extends Animal {
constructor(name:string) {
super(name);
}
eat():string {
return `The cat ${this.name} is eating fish.`
}
}
class Dog extends Animal {
constructor(name:string) {
super(name);
}
eat():string {
return `The dog ${this.name} is eating bone.`
}
}
# 3.7 抽象类和抽象方法
- 抽象类:提供其他类继承的基类,不能直接被实例化。
- 抽象方法:只能存在于抽象类中,不包含具体的实现,并且必须在派生类中实现。
- 用于定义标准
abstract class Animal {
public name: string;
// 这里有IDE提示:"abstract class constructor can be made protected."
constructor(name: string) {
this.name = name;
}
abstract eat(): any;
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat() {
return `The dog ${this.name} is eating.`;
}
}
let d = new Dog("black");
console.log(d.eat());
# 4. 接口
# 4.1 属性类接口
// 属性类接口:对传入的参数进行约束
interface FullName {
firstName: string; // 注意分号,不是逗号
lastName: string;
}
function printName(name: FullName) {
console.log(`${name.firstName}·${name.lastName}`);
}
// 限制传入的对象
// 如果传入的是字面量,则严格要求,不允许多余的对象属性
// 如果传入的是对象,除了必须包含的属性以外,允许有多余的属性存在
// 以及,接口的约束是两方面的:
// 一方面约束传入的参数
// 另一方面,在传入的参数是对象的时候,允许对象中包含多余的属性,但是并不允许函数显式使用这些多余的属性。
let obj = {
firstName: 'Tim',
lastName: 'Duncan',
pos: 'Power Forward'
};
printName({lastName: 'Town', firstName: 'Evan'});
printName(obj);
# 4.2 可选接口属性
// 接口可选属性
interface FullName {
firstName: string;
lastName?: string;
}
function getName(name: FullName) {
console.log(name);
}
getName({
firstName: "Tony",
lastName: "parker"
});
let obj = {
firstName: "Manu",
// lastName: "Ginobili",
pos: "Shooting Guard"
};
getName(obj);
// 举例:原生JS封装AJAX接口
interface Config {
type: string;
url: string;
data?: string;
dataType: string;
}
function ajax(config: Config) {
let xhr = new XMLHttpRequest();
xhr.open(config.type, config.url, true);
xhr.send(config.data);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log('Success');
if (config.dataType === 'json') {
console.log(JSON.parse(xhr.responseText));;
} else {
console.log(xhr.responseText);
}
}
};
}
# 4.3 函数类型接口
// 加密的函数类型接口
interface encrypt {
(key: string, value: string): string;
}
let md5: encrypt = function (key: string, value: string): string {
return `000${key}0010${value}1011001`;
};
let sha1: encrypt = function (key, value) {
return key + value;
};
console.log(md5('key', 'value'));
# 4.4 可索引接口
// 数组的约束
interface UserArr {
[index: number]: string;
}
let arr: UserArr = ['range', 'ray'];
// 对象的约束
interface UserObj {
[index: string]: string;
}
let obj:UserObj = {
name: 'Range',
pos: 'Point Guard'
}
# 4.5 类的类型接口
interface Animal {
name: string;
eat(str: string): void;
}
// 注意这里的 implements
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log(`The dog ${this.name} is eating bone.`);
}
}
let dog = new Dog('range');
dog.eat();
# 4.6 接口的继承
// 接口的继承
interface Animal {
eat(): void;
}
interface Person extends Animal {
work(): void;
}
class Student {
public name: string;
constructor(name: string) {
this.name = name;
}
study() {
console.log(`The student ${this.name} is doing homework.`);
}
}
class Boy extends Student implements Person {
constructor(name: string) {
super(name);
}
eat() {
console.log(`This boy ${this.name} is eating rice.`);
}
work() {
console.log(`This boy ${this.name} is working.`);
}
}
let boy = new Boy('Ray');
boy.eat();
boy.work();
boy.study();
# 5. 泛型
# 5.1 基本使用
// 泛型是用来解决类、接口、方法的可复用性,以及对不特定数据类型的支持(类型校验)。
// 希望可以同时支持 string类型 和 number类型
// 传入什么类型,就返回什么类型
// T表示泛型
function getData<T>(value:T):T {
return value;
}
getData<number>(123);
# 5.2 类的泛型
class MinClass<T> {
public list: T[] = [];
add(num: T): void {
this.list.push(num);
}
min(): T {
let minNum = this.list[0];
for (let i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i];
}
}
return minNum;
}
}
let m = new MinClass<number>();
m.add(523);
m.add(619);
m.add(2043);
console.log(m.min());
# 5.3 函数类型接口
interface ConfigFn {
(value1: string, value2 :string):string;
}
let setData:ConfigFn = function (value1:string, value2:string) :string {
return value1 + value2;
}
# 5.4 泛型接口
// 第一种定义泛型接口的方法
interface ConfigFn {
<T>(value: T): T;
}
let getData: ConfigFn = function <T>(value: T): T {
return value;
};
getData<string>('Ray');
// 第二种定义泛型接口的方法
interface ConfigFn<T> {
(value: T): T;
}
function getData<T>(value: T): T {
return value;
}
let myGetData:ConfigFn<string> = getData;
console.log(myGetData('Ray'));
# 5.5 实例
// 1.不使用泛型
class User {
username: string | undefined;
password: string | undefined;
}
class MySqlDB {
// 把类作为参数,来约束传入的参数的数据类型
add(user: User): boolean {
console.log(user)
return true;
}
}
let user = new User();
user.username = 'Ray';
user.password = '2043';
let db = new MySqlDB();
db.add(user);
// 2.使用泛型
class User {
username: string | undefined;
password: string | undefined;
}
class MySqlDB<T> {
add(user: T): boolean {
console.log(user);
return true;
}
}
let user = new User();
user.username = 'Ray';
user.password = '2043';
let hacker = {
name: 'Ray',
age: 24
}
let db = new MySqlDB<User>();
db.add(user);
# 6. 模块
# 6.1 模块的暴露和引入
// 暴露
let dbUrl = 'https://202.43.21.1';
export function getData():void {
}
export function save():void {
}
// 或者
export {getData, save, dbUrl};
// 或者
export default getData;
// 引入
import {getData, save} from "./modules/db";
// 或者
import {getData as get, save} from "./modules/db";
// 或者
import get from './modules/db';
# 6.2 命名空间
// 命名空间
namespace A {
export class Dog {
name:string;
constructor(name:string) {
this.name = name;
}
eat():string {
return `namespace A, dog ${this.name} is eating.`;
}
}
}
namespace B {
export class Dog {
name:string;
constructor(name:string) {
this.name = name;
}
eat():string {
return `namespace B, dog ${this.name} is eating.`
}
}
}
let dogA = new A.Dog('A');
let dogB = new B.Dog('B');
console.log(dogA.eat());
console.log(dogB.eat());
# 7. 装饰器
# 7.1 概念
- 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法,属性或参数上,可以修改类的行为。
- 通俗一点,装饰器是一个方法,可以注入到类、方法、属性参数上,从而实现扩展类、属性、方法、参数的功能。
- "非侵入式的行为修改。"
- 常见的装饰器类型:
- 类装饰器
- 属性装饰器
- 方法装饰器
- 参数装饰器
- 使用场景举例:
- 某一个函数被很多人用,当我们想要对这个函数执行耗时统计的时候,为了不影响其他人使用,我们自己加上一个耗时统计的装饰器,当不需要计时的时候,删除装饰器即可。
# 7.2 类装饰器
# 7.2.1 普通装饰器
// 类装饰器:普通装饰器(无法传参)
function logClass(params: any) {
// params指代的就是当前的类,也就是被装饰的类
console.log(params);
params.prototype.apiUrl = 'www.art.com';
params.prototype.run = function () {
console.log('comes from function run()');
};
}
@logClass
class HttpClient {
constructor() {
}
getData() {
}
}
let http: any = new HttpClient();
console.log(http.apiUrl);
http.run();
# 7.2.2 装饰器工厂
// 类装饰器:装饰器工厂(可传参)
function logClass(params: string) {
// 注意这里的params不再是被装饰的类,而是传给装饰器的参数
return function (target: any) {
// 这里面的target才是被装饰的类
console.log(target);
console.log(params);
target.prototype.apiUrl = `apiUrl: ${params}`;
};
}
@logClass('https://www.art.com')
class HttpClient {
constructor() {
}
getData() {
}
}
let http: any = new HttpClient();
console.log(http.apiUrl);
# 7.2.3 重载构造函数
// 类装饰器:类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
// 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明
function logClass(target: any) {
return class extends target {
// 其实这里不是完全的替换,而是做了一个类的继承
apiUrl: any = "apiURL in decorator.";
getData() {
console.log(`Decorator: ${this.apiUrl}`);
}
};
}
@logClass
class HttpClient {
public apiUrl: string | undefined;
constructor() {
this.apiUrl = 'apiURL in constructor.';
}
getData() {
console.log(`Constructor: ${this.apiUrl}`);
}
}
let http = new HttpClient();
console.log(http.apiUrl)
http.getData();
# 7.3 属性装饰器
// 对于被装饰的类,即使是没有实例化一个实例,装饰器中的代码也会被执行
function logProperty(params: any) {
return function (target: any, attr: any) {
console.log(target);
console.log(attr);
// target[attr] = params;
// 但是不能写成 target.attr = params;
// 而是要写成 target.url = params;
};
}
// @logClass('Nothing')
class HttpClient {
@logProperty('Ray')
public url: any | undefined;
// 这里的 url 就是一个属性
constructor() {
}
getData() {
console.log(this.url);
}
}
let http: any = new HttpClient();
http.getData();
# 7.4 方法装饰器
// 方法装饰器
// 它会被应用到方法的属性描述符上,可以用于监视、修改、替换方法定义
// 方法装饰器会在运行时传入下列3个参数:
// 1.类的构造函数
// 2.成员的名字
// 3.成员的属性描述符: writable, enumerable, configurable ...
function logMethods(params: any) {
return function (target: any, methodName: any, methodDesc: any) {
console.log(target); // 原型对象
console.log(methodName); // 方法名称
console.log(methodDesc); // 方法描述
console.log(methodDesc.value); // 被装饰的方法
let oMethod = methodDesc.value;
methodDesc.value = function (...args:any[]) {
console.log('Method has been changed by decorator. XD')
oMethod.apply(this, args)
}
target.apiUrl = 'apiUrl_apiUrl';
target.run = function () {
console.log('Running... QAQ');
};
};
}
class HttpClient {
public url: any | undefined;
constructor() {
}
@logMethods('https://www.art.com')
getData(str:string) {
console.log(`${str}, getting data... TAT`);
}
}
let http: any = new HttpClient();
http.run();
console.log(http.apiUrl);
http.getData('Hey');
# 7.5 参数装饰器
// 方法参数装饰器
// 参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列3个参数:
// 1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
// 2.方法的名字
// 3.参数在函数参数列表中的索引。
function logParams(params: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log(params);
console.log(target);
console.log(methodName);
console.log(paramsIndex);
};
}
class HttpClient {
public url: any | undefined;
constructor() {
}
getData(@logParams('decorator_uuid') uuid: any) {
console.log(`Methods in getData. uuid: ${uuid}`);
}
}
let http = new HttpClient();
http.getData(2043);
# 10.一些自己的思考
- 强类型语言,但仅仅是编程时候,或者说是提供了一种js下强类型的编程范式,但是也可以不遵守
- 因为最终ts的代码是被转化为es5的js代码,然后被浏览器执行的,所以非要说的话,其实本质上并没有发生改变
- 即使IDE/tsLint可以检查出ts中的语法错误,但仍然是可以执行的,这些都是由于TS基于JS,而JS的本质是弱类型语言以及JS的一些其他的特定,决定了运行时的一些效果。
- 重构代码的过程中,至于性能还有待测试,但是避免了在类型上出错,同时这种面向对象的编程思想,也增强了代码的可读性,对于大型项目而言降低了维护成本,虽然重构的时候ESLint搞得人很痛苦。