此次在上次写的TypeScript基础语法基础上,做了一些补充,接着是语法的进阶。
补充
数组写法2
// 数组写法1
const arr2: (number | string)[] = [1, 2, 'abe', 3];
// 数组写法2
const arr1: Array = ['abcd', 2, 3];
枚举类型
// 枚举
enum Size {
Small,
Middle,
Large,
XLarge
}
console.log(Size.Small, Size.Middle, Size.Large, Size.XLarge);
// 0 1 2 3
也可以自定义枚举值。
// 自定义枚举值
enum Size {
Small = 1,
Middle,
Large = 7,
XLarge
}
console.log(Size.Small, Size.Middle, Size.Large, Size.XLarge);
// 1 2 7 8
反向映射。
// 反向映射
enum Size {
Small = 1,
Middle,
Large = 7,
XLarge
}
console.log(Size[1], Size[2], Size[7], Size[8]);
/**
* 获取索引对应的属性,如果是自定义的索引,要根据自定义的来获取
* Small Middle Large XLarge
*/
object类型
// object
let obj = {
name: 'Tom'
};
function getName(obj: object): void {
console.log(obj);
}
getName(obj);
// { name: "Tom" }
Symbol类型
Symbol基础
const s = Symbol();
console.log(s);
// Symbol()
const s3 = Symbol('abcd');
console.log(s3);
// Symbol(abcd)
const s4 = Symbol('abcd');
console.log(s4);
// Symbol(abcd)
// 虽然s3和s4看起来一样,但比较,s3!=s4
// Symbol值不能用于运算,但是可以转化为boolean值或者string值
console.log(s4.toString()); // "Symbol(abcd)"
console.log(!s4); // false
console.log(Boolean(s4)); // true
Symbol作为属性名
Symbol一般用来标记属性名的唯一性。
// 对象属性举例
let prop = 'name';
const obj2 = {
name: 'Jerry',
[`my_${ prop }_is`]: 'Tom'
};
console.log(obj2);
// {name: "Jerry", my_name_is: "Tom"}
// Symbol作为属性名
const s5 = Symbol('name');
const obj3 = {
age: 23,
[s5]: 'Tom'
}
console.log(obj3);
// {age: 23, Symbol(name): "Tom"}
// 获取对象的属性,直接obj.s5获取不到Symbol类型的属性值,需要使用[]获取
console.log(obj3.s5); // error
console.log(obj3[s5]); // name
属性名的遍历
以下这些都是无法获取到Symbol类型属性名的情况。
// 1、for in循环不出Symbol值属性名
const s5 = Symbol('name');
const obj3 = {
age: 23,
[s5]: 'Tom'
}
for (let key in obj3) {
console.log(key);
}
// age
// 2、Object.keys(obj3)也没法获取Symbol值属性名
console.log(Object.keys(obj3)); // ["age"]
// 3、Object.getOwnPropertyNames(obj3)也无法获取Symbol值属性名
console.log(Object.getOwnPropertyNames(obj3)); // ["age"]
// 4、JSON.stringify把一个对象转化为字符串,也无法获取Symbol 值属性名
console.log(JSON.stringify(obj3)); // '{"age":23}'
以下这些是可以获取到Symbol类型属性名的情况
// 1、Object.getOwnPropertySymbols(obj3)会返回对象中所有Symbol 值属性名
console.log(Object.getOwnPropertySymbols(obj3)); // [Symbol(name)]
// 2、Reflect.ownKeys(obj3)会返回包含Symbol值在内的属性名
console.log(Reflect.ownKeys(obj3)); // ["age", Symbol(name)]
Symbol的两个静态方法
Symbol.for
const s6 = Symbol('Tom');
const s7 = Symbol('Tom');
// s6 === s7 false
const s8 = Symbol.for('Tom');
const s9 = Symbol.for('Tom');
// s8 === s9 true
Symbol.keyFor
// Symbol.keyFor() 与Symbol.for()对应,
// Symbol.keyFor()能获取Symbol.for()的属性,
// 但不能获取单纯的Symbol()的属性
console.log(Symbol.keyFor(s6)); // undefined
console.log(Symbol.keyFor(s8)); // Tom
类型断言
类型断言指的是强行把一个变量类型指定为所需要的类型。
举个例子,写一个函数,函数接受一个参数,参数类型可以是字符串或者数值,这个函数要返回参数的长度。按照ts的写法,应该是这样:
const getLength = (target: string | number): number => {
// string
if (target.length || target.length === 0) {
return target.length;
// number
} else {
return target.toString().length;
}
}
但这样写有问题,因为target的类型限定了是string或number,由于number类型的参数是没有length属性的,这样ts就会当作这个参数是没有length属性的,这时,可以强行把函数体里的target类型写成string类型,类型断言可以做到。
类型断言的实现方式是在需要类型断言的变量前面,使用<类型>的形式,或者使用as把变量指定为某类型。
const getLength = (target: string | number): number => {
// string
if ((target).length || (target as string).length === 0) {
return (target).length;
// number
} else {
return target.toString().length;
}
}
}
console.log(getLength(1230000)); // 7
console.log(getLength('123')); // 3
类型断言的缺点是需要在变量出现的每个地方进行类型断言,使用自定义类型保护可以替换类型断言的做法。
进阶(一)
接口
定义接口的形式
对象形式的接口
interface ModalWidth {
width: number,
unit: string,
background?: string,
readonly id: string
}
const getModalWidth = ({ width, unit }: ModalWidth): string => {
return width + unit;
}
console.log(getModalWidth({
width: 100,
unit: 'px',
id: 'A0001'
}));
// "100px"
当允许变量(这里指对象)中的属性多于接口(对象形式的接口)中定义的属性时,有三种方法解决接口属性的校验问题,类型断言、类型兼容性、索引签名[prop: string]: any。
类型断言
console.log(getModalWidth({
width: 100,
unit: 'px',
id: 'A0001',
height: 200
} as ModalWidth)); // as类型断言
// "100px"
类型兼容性
所谓的类型兼容性,是指把对象字面量改写成先用一个变量存储含有数据的对象,再把这个变量作为参数传到函数里。
举个例子,变量b存储了a对象,a中的属性可以多于b的,也就是传给函数func(b)的参数对象b中的属性只能多,不能少。
const b = a;
func(b);
所以
const modalWidth = {
width: 100,
unit: 'px',
id: 'A0001',
height: 200
}
console.log(getModalWidth(modalWidth));
// "100px"
索引签名
interface ModalWidth {
width: number,
unit: string,
background?: string,
readonly id: string,
[propname: string]: any
}
console.log(getModalWidth({
width: 100,
unit: 'px',
id: 'A0001',
height: 200
}));
// "100px"
数组形式的接口
interface widthSize {
0: number,
readonly 1: string
}
const widthList: widthSize = [100, 'px'];
console.log(widthList);
// [100, "px"]
函数形式的接口
interface addXY {
(x: number, y: number): number
}
const XAndY: addXY = (x, y) => {
return x + y;
}
console.log(XAndY(1, 1));
// 2
// 等价于类型别名的定义形式
type AddXY2 = (x: number, y: number) => number
const XAndY: AddXY2 = (x, y) => {
return x + y;
}
console.log(XAndY(1, 1));
// 2
索引形式的接口
// 数字索引
interface Shoes {
[id: number]: string
}
const shoes1: Shoes = {
17: 'AN000'
}
console.log(shoes1);
// {17: "AN000"}
// 字符串索引
interface Shoes {
[s: string]: number
}
const shoes1: Shoes = {
'size': 3
}
console.log(shoes1);
// {size: 3}
混合类型的接口
函数作为对象,也会拥有属性。
举个例子。
定义一个混合类型的接口,接口中有函数,有属性。
interface Mixture {
(): void,
type: string
}
用变量getMixture存储一个函数,这个函数有点特殊,返回值类型是刚才定义的接口Mixture。在这个函数体内,以Mixture定义的形式,再写一个函数mixture,mixture没有返回值,但有一个type属性,最终返回mixture。
const getMixture = (): Mixture => {
const mixture = () => {
console.log(mixture.type);
}
mixture.type = 'mix_type';
return mixture;
}
这样一来,getMixture就是一个返回Mixture类型的函数,一个函数返回一个函数,这就像闭包一样,最后调用方法执行一下,会输出mixture.type的值。
const getType: Mixture = getMixture();
getType();
// "mix_type"
接口继承
interface Color {
color: string
}
interface Sphere extends Color{
radius: number
}
interface Cube extends Color {
width: number
}
const sphere: Sphere = {
color: '#fff',
radius: 10
}
const cube: Cube = {
color: '#0f0',
width: 10
}
console.log(sphere);
console.log(cube);
/**
* {color: "#fff", radius: 10}
* {color: "#0f0", width: 10}
*/
函数
函数参数
可选参数
const countArgs = (x: number, y: number, z?: number): number => x + y + (z ? z : 0);
console.log(countArgs(1, 2, 3));
// 6
任意多个参数
const countArgs = (unit?: any, ..args: number[]) => {
let totals = 0;
args.forEach((item: number) => {
totals += item
});
return totals + unit;
};
console.log(countArgs(1, 2, 3, 9));
// "14px"
console.log(countArgs(1, 5, 9));
// 15
默认参数
// 默认参数
let add4 = (x: number, y: number = 0) => x + y;
console.log(add4(3, 7));
// 10
函数重载
函数重载指的是函数名相同,根据传入的参数的不同,决定不同的操作。
函数重载举例。
function getWidth(str: string): string;
function getWidth(num: number): number;
function getWidth(arg: any): any {
if (typeof arg === 'string') {
return arg;
} else {
return arg + 'px';
}
}
console.log(getWidth(15));
// "15px"
console.log(getWidth('17px'));
// "17px"
泛型
一个、多个泛型
一个泛型
const colorList = (a: number = 0, b: T): void => {
a = a + Math.ceil(Math.random() * 10);
console.log(`${ a }_${ b }`);
}
console.log(colorList(90, 'min'));
// "78_min"
多个泛型
const colorList = (a: number = 0, b: T, c: Q): void => {
a = a + Math.ceil(Math.random() * 10);
console.log(`${ a }_${ b }_${ c }`);
}
console.log(colorList(90, 'min', 0));
// "94_min_0"
类型别名定义泛型
type ColorList = (a: number, b: T) => void;
let colorArr: ColorList = (a, b) => {
console.log(`${ a }_${ b }`);
}
colorArr(10, 'px');
// "10_px"
接口中使用泛型
interface getType {
(type: T, num: number): T[]
}
// 可以把泛型提到外面,这样接口里面的属性和方法都可以用
interface getType {
(type: T, num: number): T[],
_name: T
}
泛型约束
// 泛型约束,泛型需要满足一定的条件
interface ValueWithLength {
length: number
};
// 这里的泛型约束是T含有一个length属性
const getArray6 = (arg: T, times: number = 3): T[] => {
return new Array(times).fill(arg);
}
console.log(getArray6([1, 2]));
// [[1, 2], [1, 2], [1, 2]]
console.log(getArray6({ length: 2 }));
// [{ length: 2 }, { length: 2 }, { length: 2 }]
console.log(getArray6('abc'));
// ["abc", "abc", "abc"]
在泛型约束中使用类型参数
// 在泛型约束中使用类型参数
const getArray7 = (obj: T, propname: K) => {
return obj[propname];
}
const obj1 = {
name: 'Tom',
age: 18
}
console.log(getArray7(obj1, 'age'));
// 18
类
类的只读
// readonly
class UserInfo1 {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
var user1 = new UserInfo1('Tom');
// user1.name = 'Jerry'; // 修改只读属性会报错
修饰符属性的简写
// 修饰符属性的简写
// 在constructor函数参数前面加修饰符,既可以修饰属性,同时也把属性放到实例上
class A {
constructor(public age: number) {}
}
var a1 = new A(19);
console.log(a1); // 会看到类中含有属性age
静态方法
静态方法会被类本身调用,但不能被子类继承,使用static标记静态方法。
super作为对象
在普通方法中,super对象指向父类的原型对象,在静态方法中,super对象指向父类。
静态属性
// 静态属性
class Parent6 {
public static age: number = 17;
public static getAge () {
return this.age
}
// constructor(age: number) {
// this.age = age; // 这里会报错,因为age是静态属性
// }
}
var p6 = new Parent6();
// console.log(p6.age); // error
console.log(Parent6.age); // 静态属性允许类本身访问
// 17
console.log(Parent6.getAge()); // 静态方法允许类本身调用
// 17
private属性不能被类本身访问,只可以类内部访问。
class Parent7 {
private static age: number = 17;
public static getAge (age: number) {
return this.age;
}
}
var p7 = new Parent7();
// console.log(Parent7.age); // 报错,age是private
可选属性
// 可选属性
class Parent8 {
constructor(public name: string, public age?: number, public sex?: string) {}
}
// 可选属性没有传值的话,值为undefined
var p81 = new Parent8('Tom');
console.log(p81);
// Parent8 {name: "Tom", age: undefined, sex: undefined}
var p82 = new Parent8('Tom', 18);
console.log(p82);
// Parent8 {name: "Tom", age: 18, sex: undefined}
var p83 = new Parent8('Tom', 18, 'male');
console.log(p83);
// Parent8 {name: "Tom", age: 18, sex: "male"}
类的存取器
class Parent9 {
constructor(public name: string, public age?: number, public sex?: string) {};
get nameInfo () {
return `${ this.name },${ this.age }`
}
set nameInfo (newval) {
console.log('setter: ' + newval);
this.name = newval;
}
}
var p91 = new Parent9('Tom', 19, 'man');
console.log(p91.nameInfo);
// "Tom,19"
p91.nameInfo = 'Jack';
// "setter: Jack"
console.log(p91.nameInfo);
// "Jack,19"
抽象类
// 抽象类
// 抽象类无法实例化,但能被继承
abstract class Parent10 {
constructor(public name: string) {};
public abstract printName (): void
}
class Child10 extends Parent10 {
constructor(name: string) {
super(name);
this.name = name;
}
public printName () {
console.log(this.name);
}
}
const c10 = new Child10('Tom');
console.log(c10);
// Child10 {name: "Tom"}
c10.printName();
// "Tom"
抽象类与存取器
//抽象类与存取器
abstract class Parent11 {
abstract _name: string
abstract get insideName (): string
abstract set insideName (value: string)
}
class Child11 extends Parent11 {
public _name: string = 'Jack';
public set insideName (newval: string) {
this._name = newval
}
public get insideName () {
return this._name;
}
}
var p2 = new Child11();
console.log(p2._name);
// "Jack"
p2.insideName = 'Jerry';
console.log(p2.insideName);
// "Jerry"
实例的构造函数
// 实例的构造函数
class Parent12 {
constructor(public name: string) {}
}
// const p12: Parent12 = new Parent12('Tom');
let p12 = new Parent12('Tom');
class Animals {
constructor(public name: string) {}
}
p12 = new Animals('elephent');
console.log(p12 instanceof Parent12); // false
console.log(p12 instanceof Animals); // true
类的继承
类继承接口
对于类类型接口,接口检测的是使用该接口定义的类创建的实例,省去部分定语,一句话概括,接口检测的是类的实例。
// 类继承接口
interface FoodInterface {
type: string
}
class FoodsClass implements FoodInterface {
public static type: string
}
以上这个例子,type属性是静态属性,由类本身访问,但是这个类继承了接口,接口检测的是实例,这里使用了static已经表明实例上没有这个type属性,所以会报错。
接口继承类
class Parent {
protected name: string
}
interface Inter2 extends Parent {}
class Child2 implements Inter2 {
public name: string // 受保护的属性只能在子类或者类内部访问,而Child2没有继承Parent,所以会报错
}
// 这样写不报错
class Child2 extends Parent implements Inter2 {
name: string; // 需要关掉strict
}
// 类类型使用泛型
const create = (c: new() => T): T => { // new() => T 表示创建一个类
return new c();
}
class Info7 {
age: number;
constructor() {
this.age = 18;
}
}
console.log(create(Info7).age);
// 18
枚举
数字枚举
enum Status {
Uploading,
Success,
Failed
};
console.log(Status.Uploading, Status.Success, Status.Failed);
// 0 1 2
console.log(Status['Uploading'], Status['Success'], Status['Failed']);
// 0 1 2
自定义值
const UPLOADING = 1;
enum Status {
Uploading,
Success = UPLOADING, // 这种方式,后面的属性也要自定义值
Failed = 10
};
console.log(Status.Uploading, Status.Success, Status.Failed);
// 0 1 10
console.log(Status['Uploading'], Status['Success'], Status['Failed']);
// 0 1 10
反向映射
enum Status2 {
Uploading,
Success = 9,
Failed
}
console.log(Status2[9]);
// Success
字符串枚举
enum Status3 {
Error = 'It\'s no data',
Uploading = 'uploading...',
Success = 'success',
Failed = Error
}
console.log(Status3.Failed);
// "It's no data"
异构枚举
异构枚举既含有数字枚举,还含有字符串枚举。
enum Status4 {
Status = 1,
Message = 'Success'
}
console.log(Status4);
console.log(Status4[1]);
// status
console.log(Status4['Status']);
// 1
console.log(Status4['Message']);
// Success
枚举成员类型
枚举成员定义方式
enum E { A }
enum E { A = '1' }
enum E { A = 1, B = -2 }
// 枚举成员类型
enum Animals9 {
Dog = 1,
Cat = 2
}
interface Dog {
type: Animals9.Dog
}
const dog: Dog = {
type: 1
}
console.log(dog);
// { type: 1 }
联合枚举类型
enum Status {
Off,
On
}
interface Light {
type: Status
}
const light: Light = {
// type: 0,
type: Status.Off,
// type: Animals.Dog // 报错
}
console.log(light);
// { type: 0 }
const enum
// const enum
// 这样写可以做到编译后,直接把值赋给变量,而不是对象属性的赋值
const enum Status5 {
Success = 1
}
enum Status6 {
Success = 1
}
// 值是一样的,但背后但编译结果是不一样的
console.log(Status5.Success);
// 1
// 编译结果直接是一个数值
console.log(Status6.Success);
// 1
// 编译结果是一个对象属性值