工具: PlayGround
interface接口
interface
用来定义对象的结构和类型,描述对象应该具有哪些属性和方法。 注意:
- 它仅用于声明,而不是实现; 这对于编写可重用的代码非常有用
- 它只是作为
TypeScript
的一部分,并不会转换为JavaScript
可用于:
- 数组定义
interface data {
[index:number]: number,
}
// 类型一致,没有问题
let numList_1: data = [1, 2, 3];
// Type 'string' is not assignable to type 'number'
let numList_2: data = [1, "", 3];
- 对象的定义
// 方式1
interface Data_1 {
name: string,
age: number,
}
let data_1: Data_1 = {
name: "hello",
age: 20,
}
console.log(data_1);
// 方式2
interface Data_2 {
name: string;
age: number;
// 可选属性, 表示可以选择不赋值, 该属性也可以用在作为函数参数使用
sex?: number;
// 只读属性,表示一旦赋值后不可修改
readonly id:number | string;
}
let data_2: Data_2 = {
name: "hello",
age: 20,
id : "0001",
}
data_2.age = 10;
// Error: Cannot assign to 'id' because it is a read-only property
data_2.id = 10;
- 作为类实现
interface Person {
name: string;
age: number;
greet: () => void;
}
// 通过implements实现接口
class Student implements Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
let student = new Student("ok", 20);
student.greet(); // "Hello, my name is ok"
在实际的开发中,函数传参是常用的操作,如果单纯通过注释说明,可能代码多次修改后,导致不容易维护,推荐使用interface
。
interface Person {
name: string | null, // 名字
age: any, // 岁数
sex: number, // 性别:0.男 1.女
scoreList?: number[], // 分数列表
}
function createPerson(param: Person) {
if (!param) {
return console.error("param is null");
}
let strName = param.name || "";
let age = Number(param.age) || 0;
let sex = (param.sex == 1) ? true : false;
console.log(strName, age, sex);
}
let params: Person = {
name: "TypeScript",
age: 1000,
sex: 0,
scoreList: [100, 90, 80],
}
// "TypeScript", 1000, false
createPerson(params);
在上述的代码中对数据使用了Number
的转换,主要原因在于:
// 如果数据一定为string类型,推荐使用parseInt
let age = Number.parseInt(param.age);
// 如果数据类型为any类型,推荐使用Number构造函数转换
let age = Number(param.age) || 0;
类
TypeScript 中类的使用,主要通过class
来创建类对象,通过new
来实例化类对象, 支持:
instanceof
检测对象类型- 访问权限
public
、protected
和private
- 函数重载
- 继承或重写
- 静态成员和方法
class
的本质依然是function
,但重点是将对象或函数模块化。
注: 在ES6中,对parseInt、pareseFloat 整合到了Number中,就是对模块的一种整理和优化
class Demo {
private _name: string = "";
private _age: number = 0;
// 构造函数
constructor(name:string) {
this._name = name;
}
public log() {
console.log(`Demo 的名字为${this._name}`);
}
}
let demo = new Demo("demo");
console.log(typeof(demo)); // "object"
console.log(demo instanceof Demo); // true
// 获取类名,可使用name
class Demo_1 {}
class Demo_2 {}
// "Demo_1", "Demo_2"
console.log(Demo_1.name, Demo_2.name);
注:在类中没有声明
public/private/protected
的话,默认为public
set/get对象
getter
和setter
可用于获取和设置类中的私有属性,使用它们注意:
- 必须在一个类中
- 不能用于静态属性
- 函数名字必须相同
class Demo {
private _name: string;
constructor(name: string) {
this._name = name;
}
get Name():string {
return this._name;
}
set Name(name: string) {
this._name = name;
}
}
const demo = new Demo("JavaScript");
// 获取私有属性,
console.log(demo.Name); // "JavaScript"
// 设置属性
demo.Name = "TypeScript";
console.log(demo.Name); // "TypeScript"
注:通过
set/get
对属性值的设置/获取,这是 ECMAScript 5 或 ES6 的特性, 原理是属性值会被定义为一个存取器,通过set/get
会生成对应的方法来完成设置和获取。
函数重载
类的函数重载的编写方式,不要直接实现:
class Demo {
private _name: string = "";
private _age: number = 0;
// 构造函数
constructor(name:string) {
//
}
// Error: Multiple constructor implementations are not allowed
// 原因在于如果实现,编译器创建对象不知道调用哪个构造函数
constructor(name: string, age:number) {
//
}
}
可以通过采用声明和可选参数的方式来实现:
// 实例1
class Demo {
private _name: string = "";
private _age: number = 0;
// 构造函数
constructor(name:string);
constructor(name: string, age:number);
constructor(name: string, age?:number) {
this._name = name;
if (age) {
this._age = age;
}
}
}
// 实例2:
class Calculator {
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: any, b: any): any {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
} else if (typeof a === 'string' && typeof b === 'string') {
return a.concat(b);
} else {
throw new Error('Invalid arguments');
}
}
}
const calculator = new Calculator();
console.log(calculator.add(1, 2)); // 3
console.log(calculator.add('Hello', ' World')); // Hello World
Extends继承和重写
继承的话,使用extends
, 如果对基类中的方法重新编写,就是重写
// 定义接口,简单的实例可以忽略;增加的话,有助于理解代码和重写代码相关
interface Animal {
sound(): void;
}
// 实现接口
class Cat implements Animal {
private _breed: string;
private _name: string = "Cat";
constructor(breed: string) {
this._breed = breed;
}
// 通过get和set的方式设置对象变量属性
get Name():string {
return this._name;
}
set Name(name: string) {
this._name = name;
}
sound(): void {
console.log("猫");
}
}
// 继承
class DomesticCat extends Cat {
public log() {
console.log("this is DomesticCat");
}
}
// 重写
class WildCat extends DomesticCat {
sound(): void {
console.log("野猫叫声");
}
}
const domesticCat = new DomesticCat("domesticCat");
domesticCat.Name = "短毛猫"
console.log(`cat Name: ${domesticCat.Name}`); //"cat Name: 短毛猫"
domesticCat.sound(); // 猫
//
const wildCat = new WildCat("豹猫");
wildCat.sound(); // 野猫叫声
注意:
- 对于变量,建议开头增加下划线
_
表示私有 - 可以多继承,只需要在
extends
后面添加类接口即可
注意区分implements
和extends
,前者主要用于对interface声明的方法等进行实现, 而后者主要应用于已经实现后的方法进行继承或重写。
// 声明接口
interface MyInterface {
myMethod(): void;
}
// 实现接口
class MyClass implements MyInterface {
myMethod() {
console.log("Implementing MyInterface");
}
}
const myObj = new MyClass();
myObj.myMethod(); // 输出 "Implementing MyInterface"
// interface 也可以用来定义数据结构
interface Data {
index: number,
name: string,
dataList: any[],
map: {},
}
let data:Data;
data = {
index: 1,
name : "typeScript",
dataList: [1, "2", true],
map: {a: 1, b:2},
};
console.log(data);
super
主要在子类中使用,调用基类的构造函数和方法相关
class Base {
constructor() {
console.log("Base constructor");
}
public Message() {
console.log("Base Message");
}
}
class Demo extends Base {
constructor() {
// 使用在构造函数中,必须有super
// 否则报错:Constructors for derived classes must contain a 'super' call
super();
console.log("Demo constructor");
}
public Message() {
// 可用在其他函数中调用基类的public方法相关, 不可为私有方法,否则报错
super.Message();
}
}
const demo = new Demo();
demo.Message();
/*
"Base constructor"
"Demo constructor"
"Base Message"
*/
Static
静态变量或方法在类中声明后,可以不通过new
对象直接赋值或使用,但赋值后不可在改变。
class Demo {
static value: number;
static getValue() {
console.log(`static value: ${Demo.value}`);
}
}
Demo.value = 10;
Demo.getValue(); //"static value: 10"
它常用于编写单例,特点: 为保证对象的唯一性,构造函数设置为私有。
class Singleton {
private static instance: Singleton;
private constructor() {
// 私有化构造函数,防止外部实例化
}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
public greet(): void {
console.log("Hello");
}
}
// 创建单例实例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
instance1.greet(); // "Hello"
instance2.greet(); // "Hello"
const
作为常量使用,在类中使用注意:
- 只能用于修改基本数据类型和对象属性字面量,不可用于修饰引用类型
- 如果声明
const
一定要记得初始化,不能在构造函数或其他函数进行初始化 - 声明以后,其本质就是
readonly
只读属性
class MyClass {
readonly PI: number = 3.14;
readonly config: { name: string, age: number } = { name: "John", age: 25 };
constructor() {
// this.PI = 3.14159; // 错误,无法对只读属性进行重新赋值
// this.config = { name: "Alice", age: 30 }; // 错误,无法对只读属性进行重新赋值
this.config.name = "Alice"; // 正确,可以修改对象字面量属性的值
this.config.age = 30; // 正确,可以修改对象字面量属性的值
}
printInfo(): void {
console.log(`PI: ${this.PI}`);
console.log(`Name: ${this.config.name}, Age: ${this.config.age}`);
}
}
const myObj = new MyClass();
myObj.printInfo();
// "PI: 3.14"
// "Name: Alice, Age: 30"