TS(6)-- 类型推论、类型兼容性、高级类型

2019-11-09:

学习内容:类型推论、类型兼容性、高级类型


一、类型推论:类型是在哪里如何被推断的

  在有些没有明确指出类型的地方,类型推论会帮助提供类型。如:let x = 3;  变量x的类型被推断为数字。 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时。大多数情况下,类型推论是直截了当地。

  最佳通用类型:如: let x = [0, 1, null];   两种选择:number 和 null , 计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。如果没有找到最佳通用类型的话,类型推断的结果为联合数组类型。

上下文类型:

  TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。

window.onmousedown = function(mouseEvent) {
    console.log(mouseEvent.button);  //<- Error
};

  这个例子会得到一个类型错误,TypeScript类型检查器使用Window.onmousedown函数的类型来推断右边函数表达式的类型。 因此,就能推断出 mouseEvent参数的类型了。 如果函数表达式不是在上下文类型的位置, mouseEvent参数的类型需要指定为any,这样也不会报错了。


二、类型的兼容性:

  TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义(nominal)类型形成对比。(译者注:在基于名义类型的类型系统中,数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的。这与结构性类型系统不同,它是基于类型的组成结构,且不要求明确地声明。)

  TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时,被当做是“不可靠”的。TypeScript允许这种不可靠行为的发生是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其有利的一面。

  TypeScript结构化类型系统的基本规则是,如果x要兼容y,那么y至少具有与x相同的属性。

interface Named {
    name: string;
}

let x: Named;
// y‘s inferred type is { name: string; location: string; }
let y = { name: ‘Alice‘, location: ‘Seattle‘ };
x = y;

  这里要检查y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性。 在这个例子中,y必须包含名字是namestring类型成员。y满足条件,因此赋值正确。

  (2)比较两个函数:

  如果数量及相对应类型有不同,都不能赋值。

  (3)可选参数及剩余参数:

  比较函数兼容性的时候,可选参数与必须参数是可互换的。 源类型上有额外的可选参数不是错误,目标类型的可选参数在源类型里没有对应的参数也不是错误。

当一个函数有剩余参数时,它被当做无限个可选参数。

  这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些undefinded

  (4)函数重载:

  对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。

  (5)枚举:

  枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。

  (6)类:

  类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。

class Animal {
    feet: number;
    constructor(name: string, numFeet: number) { }
}

class Size {
    feet: number;
    constructor(numFeet: number) { }
}

let a: Animal;
let s: Size;

a = s;  // OK
s = a;  // OK

  类的私有成员(private)和受保护(protected)成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。

  (7)注意:

  目前为止,我们使用了“兼容性”,它在语言规范里没有定义。 在TypeScript里,有两种兼容性:子类型和赋值。 它们的不同点在于,赋值扩展了子类型兼容性,增加了一些规则,允许和any来回赋值,以及enum和对应数字值之间的来回赋值。

  语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的,即使在implementsextends语句也不例外。


三、高级类型:

(1)交叉类型:

  将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如, Person & Serializable & Loggable同时是 Person  Serializable  Loggable。 就是说这个类型的对象同时拥有了这三种类型的成员。

  交叉类型的含义为:符合类型 A 和 B 的交叉类型的值,既符合类型 A,又符合类型 B。

  类比前文的联合类型,交叉类型可以认为是两个类型的交集。其内涵覆盖了原来两个集合的所有内涵。

(2)自定义的类型保护:

  TypeScript里的 类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

  pet is Fish就是类型谓词。 谓词为 parameterName is Type这种形式, parameterName必须是来自于当前函数签名里的一个参数名。

  每当使用一些变量调用 isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

(3)typeof的类型保护:

  这些* typeof类型保护*只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got ‘${padding}‘.`);
}

(4)instanceof 类型保护:

  instanceof类型保护是通过构造函数来细化类型的一种方式。

  instanceof的右侧要求是一个构造函数,TypeScript将细化为:

  1. 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
  2. 构造签名所返回的类型的联合

以此顺序。

interface Padder {
    getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}

class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder(4) :
        new StringPadder("  ");
}

// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder; // 类型细化为‘SpaceRepeatingPadder‘
}
if (padder instanceof StringPadder) {
    padder; // 类型细化为‘StringPadder‘
}

(5)类型别名:(尽量少用,多用接口)

  类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === ‘string‘) {
        return n;
    }
    else {
        return n();
    }
}

  类型别名可以像接口一样;然而,仍有一些细微差别。

其一,接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。

另一个重要区别是类型别名不能被 extends和 implements(自己也不能 extends和 implements其它类型)。 因为 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名

(6)字符串(/数字)字面量类型:

  允许你指定字符串必须的固定值。 在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 通过结合使用这些特性,你可以实现类似枚举类型的字符串。

type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if (easing === "ease-in") {
            // ...
        }
        else if (easing === "ease-out") {
        }
        else if (easing === "ease-in-out") {
        }
        else {
            // error! should not pass null or undefined.
        }
    }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

(7)可辨识联合:

  你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式,它也称做 标签联合或 代数数据类型。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:

  1. 具有普通的单例类型属性— 可辨识的特征
  2. 一个类型别名包含了那些类型的联合— 联合
  3. 此属性上的类型保护。
interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}

  每个接口都有 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;
    }
}

(8)多态的this 类型:

  多态的 this类型表示的是某个包含类或接口的 子类型。 这被称做 F-bounded多态性。 它能很容易的表现连贯接口间的继承,

  由于这个类使用了 this类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。

(9)索引类型:

  使用索引类型,编译器就能够检查使用了动态属性名的代码。

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map(n => o[n]);
}

interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: ‘Jarid‘,
    age: 35
};
let strings: string[] = pluck(person, [‘name‘]); // ok, string[]

  编译器会检查 name是否真的是 Person的一个属性。 本例还引入了几个新的类型操作符。 首先是 keyof T, 索引类型查询操作符。 对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合。正如:

  keyof Person是完全可以与 ‘name‘ | ‘age‘互相替换的。 不同的是如果你添加了其它的属性到 Person,例如 address: string,那么 keyof Person会自动变为 ‘name‘ | ‘age‘ | ‘address‘

  T[K], 索引访问操作符。 在这里,类型语法反映了表达式语法。 这意味着 person[‘name‘]具有类型 Person[‘name‘] — 在我们的例子里则为 string类型。 然而,就像索引类型查询一样,你可以在普通的上下文里使用 T[K],这正是它的强大所在。 你只要确保类型变量 K extends keyof T就可以了。

(10)映射类型:

  TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。 例如,你可以令每个属性成为 readonly类型或可选的。

(后续补充。。。)

原文地址:https://www.cnblogs.com/marvintang1001/p/11828700.html

时间: 2024-10-08 21:47:03

TS(6)-- 类型推论、类型兼容性、高级类型的相关文章

TypeScript入门-枚举、类型推论

枚举 使用枚举可以定义一些具有名字的数字常量,和在C语言中一样都是使用关键字enum enum Direction { Up = 1, Down = 1<<2, Left, Right } 注意:一个枚举类型可以有多个枚举成员,每个枚举成员都有一个对应的数字值,这个数字值可以是常数或者是计算得出的值.当满足下列条件时,枚举成员的数字值被认为是常数 不具有初始化函数并且之前的枚举成员是常数. 在这种情况下,当前枚举成员的值为上一个枚举成员的值加1. 但第一个枚举元素是个例外. 如果它没有初始化方

从零学scala(九)类型参数、高级类型

一:类型参数 泛型类 //泛型类,基本和java是一致的          class Pair[T,S](val first:T,val second:S) val pair1 = new Pair("42",42)          val pair2 = new Pair[Any,Any](42,"42") 泛型函数 //返回数组中间的值          def getMiddle[T](a:Array[T]) = a(a.length/2) def mai

TS学习随笔(二)-&gt;类型推论,联合类型

这篇内容指南:        -----类型推论  -----联合类型 类型推论 第一篇中我们看了TS的基本使用和基本数据类型的使用,知道了变量在使用的时候都得加一个类型,那我们可不可以不加呢,这个嘛,可以也不可以,为啥这木说呢,各位看官我们上眼瞧一下. 首先我们要来先了解一个概念,类型推论:如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型 例子一: let myFavoriteNumber = 'seven'; myFavo

从C#到TypeScript - 高级类型

C# vs TypeScript - 高级类型 上一篇讲了基础类型,基本上用基础类型足够开发了,不过如果要更高效的开发,还是要看下高级类型,这篇和C#共同点并不多,只是延用这个主题. 联合类型 可以从字面上进行理解:其实就是多个类型联合在一起,用|符号隔开.如: string | number, 表示希望这个类型既可以是string,又可以是number.联合类型的字段只能调用这些类型共同拥有的方法,除非类型推论系统自动判断出真正的类型. //这里sn就是一个联合类型的字段,由于类型推论推断出s

013 --TypeScript之高级类型

交叉类型可以简单理解为将多个类型合并成一个类型 function extend<T, U>(first: T, second: U): T & U { let result = {} as T & U for(let id in first) { result[id] = first[id] as any } for(let id in second){ if(!result.hasOwnProperty(id)){ result[id] = second[id] as any

typeScript-基础知识-4-3类型推论

类型推论: 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型. 什么是类型推论 以下代码虽然没有指定类型,但是会在编译的时候报错: let myFavoriteNumber = 'seven'; myFavoriteNumber = 7; // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'. 事实上,它等价于: let

scala一些高级类型

package com.ming.test import scala.collection.mutable.ArrayBuffer import scala.io.Source import java.awt.image.BufferedImage import javax.imageio.ImageIO import java.io.File /** * 高级类型 */ //单例类型,链式调用 class Document{ def setTitle(title:String)={this}

5.任意值、类型推论、联合类型

1.任意值any,可以将任意类型的变量赋值给该类型的变量,没有指定类型的变量默认为any 也可以用来定义不定类型的数组 2.上面说到没有指定类型的变量默认为any类型的,但是当声明变量时定义了该变量值,则默认变量为该值得类型值,这就是类型推论 若声明时未定义,则不会 3.联合类型即可以取值为多种类型中的一种 下面的例子顶你声明了一个联合类型的变量,其类型可以是string和number中的一种,不可以为其他类型,其间用"|"隔开

【Go入门教程3】基本类型 和 高级类型

基本类型 Go 有很多预定义类型,这里简单地把它们分为 基本类型 和 高级类型.Go 的基本类型并不多,而且大部分都与整数相关,如下表所示: 名 称 宽度(字节) 零 值 说 明 bool 1 false 布尔类型,其值不为真即为假.真用常量 true 表示,假由常量 false 表示 byte 1 0 字节类型,它也可以看作是一个由 8 位二进制数表示的无符号整数类型 rune 4 0 rune 类型,它是有 Go 语言定义的特有的数据类型,专用于存储 Unicode 字符.它也可以看作一个由