前言:根据资料类型来学习
- (已讲完) 原始资料型别(Primitive types) : string (字串)、number (数值)、boolean (布林值)、null、undefined
- string (字串)、number (数值)、boolean (布林值) 没什么好讲的,跟 JavaScript 一样~~
null、undefined: typescript 配置与设定
- (已讲完) TypeScript 才有的型别: any、unknown、void、 never、 union types (联合型别) 、intersection types(交集型别)、 literal types (字面值型别)、 tuple (元组)、 enums (列举)
any、unknown、 never: typescript 配置与设定- TypeScript 才有的型别讲解
- (这期讲) 物件型别(Object types): object (物件) 、 arrays (阵列) 、function (函式)
上期讲 function TypeScript 才有的型别讲解
本期讲 object (物件)
interfaces---定义物件的型别
?:可选属性
readonly:防止属性被修改
interface IPerson {
name?: string;
readonly age: number;
}
const iris: IPerson = {
name: 'Jack',
age: 18
};
interface 还可以定义阵列及函式
//阵列
interface INumberArray {
[index: number]: number;
}
const list: INumberArray = [1, 1, 2, 3, 5];
//函式
interface IPersonFunc {
(name: string, age: number): void;
}
interface 可以 function overload
interface IPoint {
getDist(): number;
getDist(x?: number): number;
}
const point:IPoint = {
getDist(x?: number) {
if (x && typeof x == "number") {
return x;
} else {
return 0;
}
}
}
console.log(point.getDist(20)); //20
console.log(point.getDist()); //0
Index Signatures----新增任意属性
语法:[propName: string]
所有成员属性格式都必须符合 Index Signatures
// error example
interface NumberDictionary {
[index: string]: number;
length: number; // ok
name: string; //error: Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}
// ok example
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
泛型(Generics)---- 不预先指定具体的型别而在使用的时候再指定型别的一种特性
Generics实现了类型自由但是又不会出现any,undefined和null的缺点
除了使用「型别 + 方括号」来表示 array, 也可以使用泛型方式来表示
在函式后面加上 <Type> 表示动态型别,<Type>命名也是可以自行定义的,如<List>。只是 <T> 及 <Type> 比较通用。
//「型别 + 方括号」
const list1: number[] = [1, 2, 3];
//阵列泛型
const list2: Array<number> = [1, 2, 3];
//「型别 + 方括号」
function identity2(arg: number[]): number[] {
console.log(arg);
return arg;
}
identity2([1, 2, 3]); // [1, 2, 3]
//阵列泛型
function identity(arg: Array<number>): Array<number> {
console.log(arg);
return arg;
}
identity([1, 2, 3]); // [1, 2, 3]
//arrow function
const makeTuple2 = <T, U>(tuple: [T, U]): [T, U] => {
return [tuple[0], tuple[1]];
}
const tuple2 = makeTuple2([1, "a"]);
console.log(tuple2); //[ 1, 'a' ]
Generic Constraints 泛型约束
在函式内部使用泛型变数的时候,由于事先不知道它是哪种型别,所以不能随意的操作它的属性或方法。
可以用 extends解决
interface Lengthwise {
length: number;
}
function loggingIdentity2<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity2(3); //error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
loggingIdentity2({ length: 10, value: 3 });
Generic Interface
interface GenericIdentityFn {
<Type>(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
Generic Classes
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
//限制为 number 型別
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
Generic Function 带入不同的值对应的型别
以下例子想要取得带入参数 array 第 0 个的值, 我们可以看到 s 回传值的型别会是any, 但我想要带入不同的值对应的型别,该怎么做呢。
function firstElement(arr: any[]) {
return arr[0];
}
const s = firstElement(["a", "b", "c"]);
const n = firstElement([1, 2, 3]);
console.log(s); //a
console.log(n); //1
我们可以使用泛型,我们在函式名后添加了,其中 Type 用来指代任意输入的型别,让型别推论自动推算出来。可以看到变数 s 会是 string 型别。而 n 会是 number 型别。
function firstElement<Type>(arr: Type[]): Type {
return arr[0];
}
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
console.log(s); //a
console.log(n); //1
Inference ---泛型有了类型推断再也不用担心不够灵活了
除了使用 <Type> 的方式,可以传入多组参数如<Input, Output>,让 TypeScript 自动 inferred 型别。
如下,n被 inferred 为string, 而透过 parseInt 字串转数字,最后 parsed 被 inferred 的型别是number[]。Input及 Output 命名可以自行定义。
function map<Input, Output>(
arr: Input[],
func: (arg: Input) => Output
): Output[] {
return arr.map(func);
}
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n, 10));
console.log(parsed); //[1,2,3]
指定参数型别
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
const arr = combine([1, 2, 3], ["hello"]);
//❌ error: Type 'string' is not assignable to type 'number'.
const arr = combine<string | number>([1, 2, 3], ["hello"]);
console.log(arr); //[1, 2, 3, "hello"]
// ⭕ 指定他为string | number 联合型别。
Class
Class简单来说就是物件的模板,定义了一件事物的抽象特点,包含它的属性和方法,提供一个更简洁的语法来建立物件和处理继承
一些 Class 要知道的事
- constructor: 用来初始建构一个类别 (class) 的物件。一个 class 只能有一个 constructor。(是可以不指定 constructor 的,如果不指定 constructor, 就会就会使用预设的 constructor,长这样
constructor() {}) - extends: 使用 extends 继承类别, 建立子类别,子类别可以使用父层的东西。
- super: 可以使用 super 于通过函式存取父层,super 关键字必须出现在 this 关键字之前使用
- prototype methods : 建立 class 的方法
- getter : 使用关键字 get,用以改变属性的读取
- setter : 使用关键字 set,用以改变属性的赋值(- getter & setter 的好处就是,不会改变原本 constructor 的东西,也可以直接像使用 class 的属性一样去使用。)
- static method: 使用关键字 static 在 class 建立静态方法,在 class 中的其他方法都不能使用他,静态方法常被用来建立给应用程式使用的工具函数。
- 物件(Object):类别的实例,透过 new 产生
- 抽象类别(Abstract Class):抽象类别是供其他类别继承的基底类别,抽象类别不允许被实例化。抽象类别中的抽象方法必须在子类别中被实现
- 介面(Interfaces):不同类别之间公有的属性或方法,可以抽象成一个介面。介面可以被类别实现(implements)。一个类别只能继承自另一个类别,但是可以实现多个介面。
- Fields: 在 ES6 中, 是无法直接在 class 中定义属性,必须定义在 constructor 里面并使用
this.,而 ES7 这可以直接在 calss 中定义, 而 TypeSript 也跟上脚步,可以直接在 class 里面去定义属性,且 public 公用的。 - readonly : 可以使用 readonly 关键字来唯读属性, 防止进行赋值
class Point {
x: number; //可以使用实例属性
y: number;
readonly name: string = "show point"; //readonly唯读
constructor(x = 0, y = 0) { //预设给0
this.x = x;
this.y = y;
}
printPoint() {
console.log(`x: ${this.x}, y: ${this.y}`);
}
}
let p = new Point(2, 4);
p.printPoint(); //x: 2, y: 4
p.name = "hihi"; //若对readonly属性赋值会报错 error: Cannot assign to 'name' because it is a read-only property.
--strictPropertyInitialization(tsconfig.json) : class 实例属性的赋值检查。如下面例子,已宣告 name 属性型别,却没给予赋值, 就会报错提醒。
class GoodGreeter {
name: string; //error: Property 'name' has no initializer and is not definitely assigned in the
word: string;
constructor() {
this.word = "hello";
}
}
Constructors型别定义
constructors: 他跟 function 的使用很像, 你可以使用型别注记,预设参数
Index Signatures
在 JavaScript 中使用 [] 来操作 object 属性,那些属性 JavaScript 都会要 toString 去读取, 如下方例子, 使用 obj["name0"] 没问题,使用 obj[name1] 则会报错。
let obj = {
name0: "iris",
name1: "iris",
2: "iris"
};
console.log(obj["name0"]); //iris
console.log(obj[2]); //iris
console.log(obj[name1]); //error: name1 is not defined
在 TypeScript 中, 会强制提醒使用 toString,减少我们的错误。
TypeScript 的 Index Signatures 必须是 string 或者 number。
let obj2 = {
toString() {
console.log('toString called');
return 'Hello';
}
};
class Foo {
constructor(public message: string) {}
log() {
console.log(this.message);
}
}
let foo: any = {};
foo['Hello'].log(); // World
foo[obj2] = new Foo('World'); //error: Type '{ toString(): string; }' cannot be used as an index type.
公共 & 私人 & 保护
- 都没写的话,预设值为 public
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,预设所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在宣告它的类别的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类别中也是允许被访问的
私有构造函数
该类别不允许被继承或者实例化:
class Person2 {
public name:string;
private constructor (name:string) {
this.name = name;
}
}
class Child2 extends Person2 { //无法被继承 error: Cannot extend a class 'Person2'. Class constructor is marked as private.
constructor (name) {
super(name);
}
}
let a2 = new Child2('iris');
Abstract 抽象
抽象类别中的抽象方法必须被子类别实现:
abstract class Animal2 {
public name;
public constructor(name:string) {
this.name = name;
}
public abstract sayHi():void;
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`);
}
}
let cat = new Cat('Tom');
static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接透过类别来呼叫:
class Animal3 {
public name;
public constructor(name:string) {
this.name = name;
}
static isAnimal(c:object) {
return c instanceof Animal;
}
}
let c = new Animal3('Tom');
let d = Animal3.isAnimal(c);
console.log(d); //true
c.isAnimal(c); //c.isAnimal is not a function
static 也能够继承
class Base2 {
static getGreeting() {
return "Hello world";
}
}
class Derived2 extends Base2 {
myGreeting = Derived2.getGreeting();
}
let derived2 = new Derived2();
console.log(derived2)
Class 与 Interface
我们在前面的时候有提过 interface 是用来定义物件的型别,对物件的形状进行描述。在物件导向程式语言中,介面(Interfaces)是一个很重要的概念,它是对行为的一种抽象,而具体如何行动则需要由类别(class)去实现(implement) 。
我们可以使用 implements 来检查是否有满足介面的形状。
interface Alarm {
alert();
}
//门是一个类别
class Door {
}
//防盗门是门的子类别, 有一个报警器的功能, 新增报警方法
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
//车的类别也有报警器的功能,新增报警方法
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
const car = new Car();
car.alert();//Car alert
一个类别可以实现多个介面
可以使用 , 来实现多个介面:
interface Alarm2 {
alert():void;
}
interface Light2 {
lightOn():void;
lightOff():void;
}
//Car 实现了 Alarm 和 Light 介面,既能报警,也能开关车灯。
class Car2 implements Alarm2, Light2 {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
const car2 = new Car2();
car2.lightOn(); //Car light on
介面继承介面
介面与介面之间可以是继承关系:
interface Alarm3 {
alert():void;
}
interface LightableAlarm extends Alarm3 {
lightOn():void;
lightOff():void;
}
class Car3 implements LightableAlarm {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
const car3 = new Car3();
car3.alert(); //Car alert
介面继承类别
介面也可以继承 class:
class Point {
x: number;
y: number;
constructor(x = 0,y = 0){
this.x = x;
this.y = y;
}
}
interface Point3d extends Point {
z: number;
}
const point3d: Point3d = {x: 1, y: 2, z: 3};
console.log(point3d); //{ x: 1, y: 2, z: 3 }
小声说
基础篇先到这喔~~ 乖乖打好地基,我们下次见啦~