这次在上次写的TypeScript基础语法基础上,作了一些补充,接着是语法的进阶。数组
// 数组写法1 const arr2: (number | string)[] = [1, 2, 'abe', 3]; // 数组写法2 const arr1: Array<number | string> = ['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
反向映射。dom
// 反向映射 enum Size { Small = 1, Middle, Large = 7, XLarge } console.log(Size[1], Size[2], Size[7], Size[8]); /** * 获取索引对应的属性,若是是自定义的索引,要根据自定义的来获取 * Small Middle Large XLarge */
// object let obj = { name: 'Tom' }; function getName(obj: object): void { console.log(obj); } getName(obj); // { name: "Tom" }
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通常用来标记属性名的惟一性。ide
// 对象属性举例 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类型属性名的状况。函数
// 一、for in循环不出Symbol值属性名 const s5 = Symbol('name'); const obj3 = { age: 23, [s5]: 'Tom' } for (let key in obj3) { console.log(key); } // age // 二、Object.keys(obj3)也无法获取Symbol值属性名 console.log(Object.keys(obj3)); // ["age"] // 三、Object.getOwnPropertyNames(obj3)也没法获取Symbol值属性名 console.log(Object.getOwnPropertyNames(obj3)); // ["age"] // 四、JSON.stringify把一个对象转化为字符串,也没法获取Symbol 值属性名 console.log(JSON.stringify(obj3)); // '{"age":23}'
如下这些是能够获取到Symbol类型属性名的状况this
// 一、Object.getOwnPropertySymbols(obj3)会返回对象中全部Symbol 值属性名 console.log(Object.getOwnPropertySymbols(obj3)); // [Symbol(name)] // 二、Reflect.ownKeys(obj3)会返回包含Symbol值在内的属性名 console.log(Reflect.ownKeys(obj3)); // ["age", Symbol(name)]
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.for()对应, // Symbol.keyFor()能获取Symbol.for()的属性, // 但不能获取单纯的Symbol()的属性 console.log(Symbol.keyFor(s6)); // undefined console.log(Symbol.keyFor(s8)); // Tom
类型断言指的是强行把一个变量类型指定为所须要的类型。spa
举个例子,写一个函数,函数接受一个参数,参数类型能够是字符串或者数值,这个函数要返回参数的长度。按照ts的写法,应该是这样:code
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把变量指定为某类型。blog
const getLength = (target: string | number): number => { // string if ((<string>target).length || (target as string).length === 0) { return (<string>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 = <T>(a: number = 0, b: T): void => { a = a + Math.ceil(Math.random() * 10); console.log(`${ a }_${ b }`); } console.log(colorList<string>(90, 'min')); // "78_min"
多个泛型
const colorList = <T, Q>(a: number = 0, b: T, c: Q): void => { a = a + Math.ceil(Math.random() * 10); console.log(`${ a }_${ b }_${ c }`); } console.log(colorList<string, number>(90, 'min', 0)); // "94_min_0"
type ColorList = <T>(a: number, b: T) => void; let colorArr: ColorList = (a, b) => { console.log(`${ a }_${ b }`); } colorArr(10, 'px'); // "10_px"
interface getType { <T>(type: T, num: number): T[] } // 能够把泛型提到外面,这样接口里面的属性和方法均可以用 interface getType<T> { (type: T, num: number): T[], _name: T }
// 泛型约束,泛型须要知足必定的条件 interface ValueWithLength { length: number }; // 这里的泛型约束是T含有一个length属性 const getArray6 = <T extends ValueWithLength>(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 = <T, K extends keyof T>(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对象指向父类。
// 静态属性 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 = <T>(c: new() => T): T => { // new() => T 表示建立一个类 return new c(); } class Info7 { age: number; constructor() { this.age = 18; } } console.log(create<Info7>(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 Status5 { Success = 1 } enum Status6 { Success = 1 } // 值是同样的,但背后但编译结果是不同的 console.log(Status5.Success); // 1 // 编译结果直接是一个数值 console.log(Status6.Success); // 1 // 编译结果是一个对象属性值