导读:本章记录高级类型、Symbols、迭代器和生成器;通过高级类型的学习会让你的TS代码变得更加优雅,对type有更深的理解。本章会从三个问题进行记录:什么是XXX?写法是什么?作用?希望对大家也有帮助!
高级类型
交叉类型:什么是交叉类型?用法?作用
交叉类型是将多个类型合并为一个类型。
// &就是我们的写法
function extend<T,U>(first:T,second:U):T & U{
// 类型断言
let result = < T & U >{}
for (let key in second) {
(<any>result)[key] = (<any>second)[key]
}
for (let key in first) {
// 这里如果属性已存在就不使用first的
if(!(<any>result).hasOwnProperty(key)){
(<any>result)[key] = (<any>first)[key]
}
}
return result
}
class Person{
constructor(public name:string){
}
}
interface Loggable{
log:string;
}
class ConsoleLogger implements Loggable{
log:string = 'logger'
}
const result = extend(new Person('小明'),new ConsoleLogger())
console.log(result.name, result.log)
/大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用
联合类型:什么是联合类型?写法?作用?
联合类型表示一个值可以是几种类型之一
//|就是联合类型的写法,padding可以是number或者string类型
function padLeft(value:string,padding:string|number){
}
// 如果一个值是联合类型,只能访问此联合类型的所有类型里共有的成员。
interface Bird{
fly();
layEggs();
}
interface Fish{
swim();
layEggs();
}
class MaQu implements Bird{
fly() {
console.log('fly')
}
layEggs() {
console.log('layEggs')
}
}
class CaoYu implements Fish{
swim() {
console.log('swim')
}
layEggs() {
console.log('layEggs')
}
}
function getSmallPet(): Bird | Fish{
let maqu = new MaQu()
return maqu
}
let ani = getSmallPet()
ani.layEggs()
//ani.fly()类型“Bird | Fish”上不存在属性“fly”。
可以定义方法参数限制参数类型
类型保护与区分类型
类型保护是一种运行时检查,用于在编译时无法确定变量类型的情况下,确保变量是预期的类型。
用户自定义的类型保护
// 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。
interface Bird{
fly();
layEggs();
}
interface Fish{
swim();
layEggs();
}
// 定义类型保护,我们就需要定义一个函数,返回值是类型谓词parameterName is Type
function isFish(pet:Fish | Bird): pet is Fish{
return (<Fish>pet).swim != undefined
}
typeof类型保护
// 现在我们不必将 typeof x === "number"抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。
function padLeft2(value:string,padding: string|number){
if(typeof padding === 'number'){
return padding;
}
if(typeof value === 'string'){
return padding;
}
throw new Error('error')
}
// typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
instanceof类型保护
instanceof类型保护是通过构造函数来细化类型的一种方式
// instanceof的右侧要求是一个构造函数,TypeScript将细化为:
// 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
// 构造签名所返回的类型的联合
interface Padder{
getPaddingString(): string
}
class SpaceRepeatingPadder2 implements Padder{
constructor(private num:number){
}
getPaddingString(): string {
return this.num + 'num';
}
}
class StringPadder2 implements Padder{
constructor(private value:string){
}
getPaddingString(): string {
return this.value
}
}
function getRandomPadder(){
return Math.random() < 0.5 ? new SpaceRepeatingPadder2(4) : new StringPadder2("xxx")
}
let padder = getRandomPadder()
if(padder instanceof SpaceRepeatingPadder2){
padder.getPaddingString()
}
if(padder instanceof StringPadder2){
padder.getPaddingString()
}
可以为null的类型
类型检查器认为 null与 undefined可以赋值给任何类型;
这也意味着,你阻止不了将它们赋值给其它类型,就算是你想要阻止这种情况也不行。
let s:string|null = null
// let s2:string = null
// TypeScript会把 null和 undefined区别对待。 string | null, string | undefined和 string | undefined | null是不同的类型。
// 可选参数和可选属性
{
// 使用了 --strictNullChecks,可选参数会被自动地加上 | undefined:
function f(x:number,y?:number){
return x+(y || 0)
}
f(1,undefined)
// f(1,null)
// 'null' is not assignable to parameter of type 'number | undefined'.
// 可选属性和可选参数一样的处理
}
类型保护和类型断言
function broken(name:string | null):string{
function postfix(e:string){
// return name.charAt(0) + '.the'+e //对象可能为 "null"
// !就是写法:identifier!从 identifier的类型里去除了 null和 undefined:
return name!.charAt(0) + '.the'+e
}
name = name || 'bob'
return postfix('dog')
}
类型别名
类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
起别名不会新建一个类型 - 它创建了一个新 名字来引用那个类型。
// 同接口一样,类型别名也可以是泛型
type Container<T> = {value:T}
// 可以使用类型别名来在属性里引用自己
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
// 与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型
type LinkedList<T> = T & {next:(LinkedList<T> | null)}
interface Person {
name:string
}
// 有点像链表
let people : LinkedList<Person>
=
{
name:'1',
next:{
name:'2',
next:{
name:'3',
next:null
}
}
}
let s = people.name
s = people.next!.name
s = people.next!.next!.name
// 类型别名不能出现在声明右侧的任何地方
// type Yikes = Array<Yikes>; // error
类型别名和接口对比
其一,接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。
/另一个重要区别是类型别名不能被 extends和 implements(自己也不能 extends和 implements其它类型)。 因为 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。
如果无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。
字符串字面量类型
字符串字面量类型允许你指定字符串必须的固定值。
type Easing = "ease-in" | "ease-out" | "ease-in-out"
// 字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。
// 字符串字面量类型还可以用于区分函数重载:
function createElement(tagName:"img"):string;
function createElement(tagName:"input"):string;
function createElement(tagName:string):string{
return tagName;
};
数字字面量类型
type NumMe = 1 | 2 | 3
可辨别联合
//单例类型 :单例模式意味着一个类只能被实例化一次,并且提供一个全局访问点来获取这个唯一的实例。要实现单例模式,你可以在类中维护一个静态属性来存储类实例,并提供一个静态方法来获取这个实例。
// 可辨识联合
// 具有普通的单例类型属性— 可辨识的特征。
// 一个类型别名包含了那些类型的联合— 联合。
// 此属性上的类型保护。
{
interface Square{
kind:'square';
size:number;
}
interface Rectangle{
kind: 'rectangle';
width:number;
height:number;
}
interface Circle{
kind: 'circle';
radius:number;
}
type Shape = Square | Rectangle | Circle
// 每个接口都有 kind属性但有不同的字符串字面量类型。 kind属性称做 可辨识的特征或 标签。
function area(s:Shape){
switch(s.kind){
case 'square':
return s.size * s.size;
case 'rectangle':
return s.height * s.width;
case 'circle':
return Math.PI * s.radius ** 2;
}
}
完整性检查
interface Square{
kind:'square';
size:number;
}
interface Rectangle{
kind: 'rectangle';
width:number;
height:number;
}
interface Circle{
kind: 'circle';
radius:number;
}
interface Triangle{
kind:'Triangle'
}
type Shape = Square | Rectangle | Circle | Triangle;
function assetNever(x:string):never{
throw new Error("error")
}
function area2(s:Shape){
switch(s.kind){
case 'square':
return s.size * s.size;
case 'rectangle':
return s.height * s.width;
case 'circle':
return Math.PI * s.radius ** 2;
default:
return assetNever(s.kind)
}
}
多态的this类型
多态的 this类型表示的是某个包含类或接口的 子类型。 这被称做 F-bounded多态性。 它能很容易的表现连贯接口间的继承
// 如果没有 this类型, ScientificCalculator就不能够在继承 BasicCalculator的同时还保持接口的连贯性。 multiply将会返回 BasicCalculator,它并没有 sin方法。 然而,使用 this类型, multiply会返回 this,在这里就是 ScientificCalculator。
{
class BasicCalculator{
public constructor(protected value:number){
}
public currentValue():number{
return this.value
}
public add(operand:number):this{
this.value += operand
return this
}
public multiply(operand:number):this{
this.value *= operand
return this
}
}
class ScientificCalculator extends BasicCalculator{
constructor(value=0){
super(value)
}
public sin(){
this.value = Math.sin(this.value)
return this
}
}
let result = new ScientificCalculator(10)
.multiply(2)
.sin()
.add(2)
.currentValue()
console.log(result)
}
索引类型
// 使用索引类型,编译器就能够检查使用了动态属性名的代码
{
// keyof T, 索引类型查询操作符。
// 对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合。
// T[K], 索引访问操作符
function pluck<T,K extends keyof T>(obj:T,names:K[]):T[K][]{
return names.map(n=>obj[n])
}
}
索引类型和字符串索引签名
// keyof和 T[K]与字符串索引签名进行交互
{
interface Map<T> {
[key:string] : T
}
let keys: keyof Map<number>; //keys:string
let value : Map<number>['foo'] // value:number
}
映射类型、由映射类型进行推断
// 在映射类型里,新类型以相同的形式去转换旧类型里每个属性
{
type Readonly<T> = {
readonly [P in keyof T] : T[P]
}
type Partial<T> = {
[P in keyof T]?: T[P]
}
// 类型变量 K,它会依次绑定到每个属性。
// 字符串字面量联合的 Keys,它包含了要迭代的属性名的集合。
// 属性的结果类型。
// type Flags = { [K in Keys]: boolean };
type Proxy<T>={
get():T;
set(value:T):void;
}
type Proxify<T>={
[P in keyof T] : Proxy<T[P]>
}
function proxify<T>(o:T):Proxify<T>{
let proxyO = ({
} as Proxify<T>)
for (const key in o) {
proxyO[key]= ({
get:function(){
return o[key]
},
set:function(value){
o[key] = value
}
})
}
return proxyO
}
let props = {
name:'xxx',
sex:'male'
}
let proxyProps = proxify(props)
console.log(proxyProps.name.get())
proxyProps.name.set('yyy')
console.log(proxyProps.name.get())
}
预定义的有条件类型
// TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:
// Exclude<T, U> -- 从T中剔除可以赋值给U的类型。
// Extract<T, U> -- 提取T中可以赋值给U的类型。
// NonNullable<T> -- 从T中剔除null和undefined。
// ReturnType<T> -- 获取函数返回值类型。
// InstanceType<T> -- 获取构造函数类型的实例类型。
Symbols
symbol类型的值是通过Symbol构造函数创建的
// symbol类型的值是通过Symbol构造函数创建的
let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key
Symbols是不可改变且唯一的。
// Symbols是不可改变且唯一的。
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
像字符串一样,symbols也可以被用做对象属性的键。
// 像字符串一样,symbols也可以被用做对象属性的键。
let sym4 = Symbol();
let obj ={
[sym4]:'value'
}
Symbols也可以与计算出的属性名声明相结合来声明对象的属性和类成员。
// Symbols也可以与计算出的属性名声明相结合来声明对象的属性和类成员。
const getClassNameSymbol = Symbol()
class C {
[getClassNameSymbol](){
return "C"
}
}
let c = new C()
let name_ = c[getClassNameSymbol]()
方法:
// Symbol.hasInstance
// 方法,会被instanceof运算符调用。构造器对象用来识别一个对象是否是其实例。
// Symbol.isConcatSpreadable
// 布尔值,表示当在一个对象上调用Array.prototype.concat时,这个对象的数组元素是否可展开。
// Symbol.iterator
// 方法,被for-of语句调用。返回对象的默认迭代器。
// Symbol.match
// 方法,被String.prototype.match调用。正则表达式用来匹配字符串。
// Symbol.replace
// 方法,被String.prototype.replace调用。正则表达式用来替换字符串中匹配的子串。
// Symbol.search
// 方法,被String.prototype.search调用。正则表达式返回被匹配部分在字符串中的索引。
// Symbol.species
// 函数值,为一个构造函数。用来创建派生对象。
// Symbol.split
// 方法,被String.prototype.split调用。正则表达式来用分割字符串。
// Symbol.toPrimitive
// 方法,被ToPrimitive抽象操作调用。把对象转换为相应的原始值。
// Symbol.toStringTag
// 方法,被内置方法Object.prototype.toString调用。返回创建对象时默认的字符串描述。
// Symbol.unscopables
// 对象,它自己拥有的属性会被with作用域排除在外。
迭代器和生成器
对象上的 Symbol.iterator函数负责返回供迭代的值。
for..of会遍历可迭代的对象,调用对象上的Symbol.iterator方法
{
let someArray = [1,'string',false]
for (const iterator of someArray) {
console.log(someArray) //1,string,false
}
}
for..of 对比for..in
// for..of和for..in均可迭代一个列表;但是用于迭代的值却不同,for..in迭代的是对象的 键 的列表,而for..of则迭代对象的键对应的值。
let list = [4, 5, 6];
for (let i in list) {
console.log(i); // "0", "1", "2",
}
for (let i of list) {
console.log(i); // "4", "5", "6"
}
// 另一个区别是for..in可以操作任何对象;它提供了查看对象属性的一种方法。 但是 for..of关注于迭代对象的值。内置对象Map和Set已经实现了Symbol.iterator方法,让我们可以访问它们保存的值。
let pets = new Set(["Cat", "Dog", "Hamster"]);
pets["species"] = "mammals";
for (let pet in pets) {
console.log(pet); // "species"
}
for (let pet of pets) {
console.log(pet); // "Cat", "Dog", "Hamster"
}