feng xiaohan
装饰器(Decorator)

用于给类、方法、属性等添加元数据或修改其行为,清晰地表达了意图,增加代码的可读性。

注意:装饰器是一种实验性的功能,需要启用实验性选项,并且它在未来版本可能会发生变化。

需要在 tsconfig.json 中打开装饰器语法:

"compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
}

类装饰器(ClassDecorator)

可以不去破坏原有类自身的结构,而给类添加新的属性或方法。(起一个修饰的作用)

const Base: ClassDecorator = (target) => {
  // target为Http的构造函数
  target.prototype.name = "张三";
  target.prototype.func = () => {
    console.log("我是新增加的方法");
  };
};

@Base // 装饰器用@修饰(加大写字母),它会自己去调用装饰器函数,不需要我们手动再去调用
class Http {
  // ...
}

const http = new Http() as any;
http.func();
http.name;

装饰器函数内会传递一个被装饰的类的构造函数(target),有了这个构造函数就可以不去破坏原有类自身的结构,而给类添加新的属性或方法。

  • target:构造函数

有的浏览器可能不支持@Base,可以使用它的底层原理来表示:

class Http {
  // ...
}
const http = new Http() as any;
Base(Http);
http.func();
http.name;

方法装饰器(MethodDecorator、PropertyDescriptor)

可以对某个方法进行封装,简化使用方法。

import axios from "axios";

const Get = (url: string) => {
  const fun: MethodDecorator = (
    target,
    key,
    descriptor: PropertyDescriptor
  ) => {
    axios.get(url).then((res) => {
      descriptor.value(res.data);
    });
  };
  return fun;
};

class Http {
  @Get("https://sdfdsf") // 定义方法装饰器,这样直接输入url地址就可以获得值
  getList(data: any) {
    // @Get里返回的值会自动传递到参数里
    console.log(data);
  }
}
  • target:原型对象
  • key:装饰的方法名
  • descriptor:描述符

参数装饰器(ParameterDecorator)

简化获取的参数。比如要获取的数据为 data.result.list,有了参数装饰器直接使用 data 就能获取到。

import "relect-metadata";
const Get = (url: string) => {
  const fun: MethodDecorator = (
    target,
    _key: any,
    descriptor: PropertyDescriptor
  ) => {
    const key = Reflect.getMetadata("key", target);
    axios.get(url).then((res) => {
      descriptor.value(key ? res.data[key] : res.data);
    });
  };
  return fun;
};

const Result = () => {
  const fun: ParameterDecorator = (target, key, index) => {
    Reflect.defineMetadata("key", "result", target);
  };
  return fun;
};

class Http {
  @Get("https://sdfdsf")
  getList(@Result() data: any) {
    // 定义参数装饰器
    console.log(data);
  }
}
  • @Result()比@Get()优先执行。
  • relect-metadata是一个存储、操作元数据的库。
  • target:原型对象
  • key:装饰的方法名
  • index:参数所在的位置

属性装饰器(PropertyDecorator)

const Name: PropertyDecorator = (target, key) => {
  console.log(target, key);
};

class Http {
  @Name
  xiaohan: string;
}
  • target:原型对象
  • key:属性名

装饰器工厂

利用函数柯里化,向装饰器传递参数,并且与装饰器函数内传递的参数区分。

const Base = (name: string) => {
  const fn: ClassDecorator = (target) => {
    target.prototype.name = name;
    target.prototype.func = () => {
      console.log("我是新增加的方法");
    };
  };
  return fn;
};

@Base("李四")
class Http {
  // ...
}

const http = new Http() as any;
console.log(http.name); // 李四
联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种

let myFavoriteNumber: string | number;
myFavoriteNumber = "seven";
myFavoriteNumber = 7;

联合类型使用 | 分隔每个类型。

访问联合类型的属性或方法

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

function getLength(something: string | number): number {
  return something.length;
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

上例中,length 不是 stringnumber 的共有属性,所以会报错。

访问 stringnumber 的共有属性是没问题的:

function getString(something: string | number): string {
  return something.toString();
}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

let myFavoriteNumber: string | number;
myFavoriteNumber = "seven";
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错

// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.

上例中,第二行的 myFavoriteNumber 被推断成了 string,访问它的 length 属性不会报错。

而第四行的 myFavoriteNumber 被推断成了 number,访问它的 length 属性时就报错了。

交叉类型

使用&将连个类型连接,这两个类型都必须满足:

interface People {
  name: string;
  age: number;
}
interface Man {
  sex: number;
}
const person = (man: People & Man): void => {
  console.log(man);
};

person({
  name: "张三",
  age: 18,
  sex: 0,
});
类(class)

类的类型(implement)

类的类型定义使用interface,然后类使用implements实现这个 interface:

interface Options {
  el: string | HTMLElement;
}
interface VueCls {
  options: Options;
  init(): void;
}

class Vue implements VueCls {
  options: Options;
  constructor(options: Options) {
    this.options = options;
  }
  init(): void {}
}
new Vue({
  el: "#app",
});

类的继承类型(extends)

使用 extends 继承某个类,继承之后就可以用 this 访问到被继承的类里的对象或方法。

// ...(省略上面一些接口的定义)
interface VNode {
  tag: string;
  text?: string;
  children?: VNode[];
}
class Dom {
  // 创建节点的方法
  createElement(el: string) {
    return document.createElement(el);
  }
  // 填充文本的方法
  setText(el: HTMLElement, text: string | null) {
    el.textContent = text;
  }
  // 渲染函数
  render(data: VNode) {
    let root = this.createElement(data.tag);
    if (data.children && Array.isArray(data.children)) {
      data.children.forEach((item) => {
        let child = this.render(item);
        root.appendChild(child);
      });
    } else {
      this.setText(root, data.text);
    }
    return root;
  }
}
class Vue extends Dom implements VueCls {
  options: Options;
  constructor(options: Options) {
    super(); // 继承类需要写 @1
    this.options = options;
    this.init();
  }
  init(): void {
    // 虚拟dom,通过js去渲染真实dom
    let data: VNode = {
      tag: "div",
      children: [
        {
          tag: "section",
          text: "我是子节点",
        },
        {
          tag: "section",
          text: "我是子节点2",
        },
      ],
    };
    let app =
      typeof this.options.el == "string"
        ? document.querySelector(this.oprions.el)
        : this.oprions.el;
    app.appendChild(this.render(data));
  }
}
new Vue({
  el: "#app",
});

@1:super()指向的是父类,相当于调用了父类的 prototype.constructor.call。它可以向父类传参。

readonly

只能添加在属性上,让属性只可读,不可以被修改

class Vue extends Dom implements VueCls {
    readonly options: Options;
       ...
}

private

添加在属性和方法上,被添加的属性和方法只能在其内部访问(不可在其子(继承类)或实例上访问)。

class Dom {
    // 创建节点的方法
    private createElement (el: string) {
        return document.createElement(el);
    }
    ...
}

protected

添加在属性和方法上,被添加的属性和方法只能在其内部或其子类内部访问(不可在其实例上访问)。

class Dom {
    // 创建节点的方法
    protected createElement (el: string) {
        return document.createElement(el);
    }
    ...
}

public

添加在属性和方法上,所有都能访问到。

所有属性和方法的默认值。

static

添加在属性和方法上,被添加的属性和方法只能访问到同 static 的属性和方法,只能在其实例上访问。

get 和 set

通过 get 和 set 来实现一个拦截器。

  • get:获取某个值时对其进行操作。
  • set:设置某个值时对其进行操作。
class Ref {
  _value: any;
  constructor(value: any) {
    this._value = value;
  }

  get value() {
    return this._value + "aaa";
  }
  set value(newVal) {
    this._value = newVal + "bbb";
  }
}
const ref = new Ref("哈哈");

console.log(ref.value); // 哈哈aaa

ref.value = "ccc";
console.log(ref.value); // cccbbbaaa

抽象类(abstract)

abstract定义的类为抽象类,abstract定义的方法为抽象方法。

  • 抽象类无法被实例化。
  • 抽象方法只能被描述,不能进行实现。
abstract class Vue {
  name: string;
  constructor(name?: string) {
    this.name = name;
  }
  getName(): string {
    return this.name;
  }
  abstract init(name: string): void; // 只能描述
}

抽象类如果要使用,可以使用extends继承,继承的类叫做派生类。派生类如果要继承抽象类,需要将抽象类里的抽象方法都实现

class React extends Vue {
  constructor() {
    super();
  }
  init(name: string) {} // 抽象方法必须实现
  setName(name: string) {
    this.name = name;
  }
}

派生类可以被实例化:

const react = new React();
react.setName("名字");

console.log(react.getName()); // 名字
类型断言(*)

类型断言(Type Assertion)可以用来手动指定一个值的类型。

值 as 类型;

<类型>值;
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;

用途

  • 将一个联合类型断言为其中一个类型

    此时可以使用类型断言,将 animal 断言成 Fish,以此欺骗编译器,避免访问animal.swim 时报错的问题

    interface Cat {
      name: string;
      run(): void;
    }
    interface Fish {
      name: string;
      swim(): void;
    }
    
    function isFish(animal: Cat | Fish) {
      if (typeof (animal as Fish).swim === "function") {
        return true;
      }
      return false;
    }
    

    但在运行时可能会报错。总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。

  • 将一个父类断言为更具体的子类

    interface ApiError extends Error {
      code: number;
    }
    interface HttpError extends Error {
      statusCode: number;
    }
    
    function isApiError(error: Error) {
      if (typeof (error as ApiError).code === "number") {
        return true;
      }
      return false;
    }
    

    上面的例子中,声明了函数 isApiError,它用来判断传入的参数是不是 ApiError 类型,为了实现这样一个函数,它的参数的类型肯定得是比较抽象的父类 Error,这样的话这个函数就能接受 Error 或它的子类作为参数了。

    但是由于父类 Error 中没有 code 属性,故直接获取 error.code 会报错,需要使用类型断言获取 (error as ApiError).code

  • 将任何一个类型断言为 any

    一般用于使用 any 进行临时断言。

    (window as any).foo = 1;
    

    any 类型的变量上,访问任何属性都是允许的。

    需要注意的是,将一个变量断言为 any 可以说是解决 TypeScript 中类型问题的最后一个手段。

    它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any

  • 将 any 断言为一个具体的类型

    function getCacheData(key: string): any {
      return (window as any).cache[key];
    }
    
    interface Cat {
      name: string;
      run(): void;
    }
    
    const tom = getCacheData("tom") as Cat;
    tom.run();
    

    上面的例子中,我们调用完 getCacheData 之后,立即将它断言为 Cat 类型。这样的话明确了 tom 的类型,后续对 tom 的访问时就有了代码补全,提高了代码的可维护性。

限制

  • 联合类型可以被断言为其中一个类型

  • 父类可以被断言为子类

  • 任何类型都可以被断言为 any

  • any 可以被断言为任何类型

  • 要使得 A 能够被断言为 B,只需要 A 兼容 BB 兼容 A 即可

  • 类型断言并不是任何时候都可以成功,不可滥用:

    const fn = (type: any): boolean => {
      return type as boolean;
    };
    
    let cb = fn(1);
    console.log(cb); // 1
    

双重断言

除非迫不得已,千万别用双重断言。

类型断言 vs 类型转换

所以类型断言不是类型转换,它不会真的影响到变量的类型。

若要进行类型转换,需要直接调用类型转换的方法:

function toBoolean(something: any): boolean {
  return Boolean(something);
}

toBoolean(1);
// 返回值为 true

类型断言 vs 类型声明

类型声明是比类型断言更加严格的。

所以为了增加代码的质量,我们最好优先使用类型声明,这也比类型断言的 as 语法更加优雅。

类型断言 vs 泛型

function getCacheData(key: string): any {
  return (window as any).cache[key];
}

interface Cat {
  name: string;
  run(): void;
}

const tom = getCacheData("tom") as Cat;
tom.run();

我们还有第三种方式可以解决这个问题,那就是泛型:

function getCacheData<T>(key: string): T {
  return (window as any).cache[key];
}

interface Cat {
  name: string;
  run(): void;
}

const tom = getCacheData<Cat>("tom");
tom.run();

通过给 getCacheData 函数添加了一个泛型 <T>,我们可以更加规范的实现对 getCacheData 返回值的约束,这也同时去除掉了代码中的 any,是最优的一个解决方案。

类型推论和类型别名

类型推论

TypeScript 会在没有明确的指定类型的时候推测出一个类型。

以下代码虽然没有指定类型,但是会在编译的时候报错:

let myFavoriteNumber = "seven";
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

事实上,它等价于:

let myFavoriteNumber: string = "seven";
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

注意:如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查。

let myFavoriteNumber;
myFavoriteNumber = "seven";
myFavoriteNumber = 7;

类型别名(type)

使用type给类型定义别名,定义的类型可以是联合类型,void 类型,基础类型等:

type s = string | number;
let str: s = "定义类型";

与 interface 区别

interface也可以给类型进行进行定义,但是:

  • interface可以使用extends继承别的interface,而type只能使用交叉类型将别的类型合并到一起:

    type s = number[] & B;
    interface A extends B {}
    
  • interface无法在外部表示联合类型,只能在内部的属性里表示;而type很好表示联合类型:

    type s = number[] | string;
    interface A extends B {
      name: string | number;
    }
    
  • interface遇到重名的interface会自动合并;type在同一个命名空间不接受重名;

extends

type可以使用extends做包含的意思,extends左边的值作为右边类型的子类型:

type num = 1 extends number ? 1 : 0;
生成器和迭代器

生成器

function* gen() {
  // 生成器上面加*
  yield Promise.resolve("一"); // yield后面跟需要返回的值,可以同步和异步
  yield "二";
  yield "三";
  yield "四";
}

const valueGen = gen();
// 必须通过next调用,且调用顺序的值跟生成器函数里的值顺序有关,如果done为true就表示生成器里没有值可以使用了
console.log(valueGen.next()); // {value: Promise {'一'}, done: false}
console.log(valueGen.next()); // {value: '二', done: false}
console.log(valueGen.next()); // {value: '三', done: false}
console.log(valueGen.next()); // {value: '四', done: false}
console.log(valueGen.next()); // {value: undefined, done: true}

迭代器

类数组和数组元素上都会有迭代器函数的存在(Symbol.iterator())。

使用和生成器差不多,也需要用 next。

使用迭代器支持循环 Set、Map、NodeList 等伪数组的方法:

const each = (value: any) => {
  let It: any = value[Symbol.iterator]();
  let next: any = { done: false };
  while (!next.done) {
    next = It.next();
    if (!next.done) {
      console.log(next.value);
    }
  }
};

for of

上面迭代器循环的语法糖,能直接遍历 Set、Map、NodeList 等伪数组:

for (let value of arr) {
}

注意:对象不能使用for of,因为对象没有Symbol.iterator()函数。

数组解构

数组解构的底层原理实际上也是调用Symbol.iterator()来返回其中的值,然后将其分配给对应的变量。

这个过程在运行时动态进行。

对象解构实现数组解构的方式

注:不同于传统的对象解构,传统的对象解构底层并不是调用Symbol.iterator(),而是通过对象的属性访问器将对象中的每个属性值转换为一个可访问的对象,并获取每个值,再将每个值动态分配给对应的变量。

由于对象没有Symbol.iterator(),我们可以手动添加来达到一个迭代器的效果。

let obj = {
    max: 5,
    current: 0,
    [Symbol.iterator]() {
        return {
            max: this.max,
            current: this.current,
            next() {
                if(this.current == this.max) {
                    return {
                        value: undefined,
                        done: true
                    }
                }else {
                    value: this.current++,
                    done: false
                }
            }
        }
    }
}
let x = [...obj]
console.log(x); // [0,1,2,3,4]
混入(Mixins)

合并。

对象混入

可以使用 ES6 的Object.assign合并多个对象,合并后对象的类型将会变成合并前对象的类型的交叉类型

interface Name {
  name: string;
}
interface Age {
  age: number;
}
interface Sex {
  sex: number;
}

let a: Name = { name: "张三" };
let b: Age = { age: 18 };
let c: Sex = { sex: 0 };
let obj = Object.assign(a, b, c); // 类型为let obj: Name & Age & Sex

类混入

class A {
  type: boolean;
  changeType(): void {
    this.type = !this.type;
  }
}
class B {
  name: string;
  getName(): string {
    return this.name;
  }
}
class C implements A, B {
  type: boolean = false;
  name: string = "张三";
  changeType: () => void;
  getName: () => string;
}
function mixins(curClass: any, itemClass: any[]) {
  itemClass.forEach((item) => {
    Object.getOwnPropertyNames(item.prototype).forEach((name) => {
      // 将另一个类里的函数赋值给实现类里的函数
      curClass.prototype[name] = item.prototype[name];
    });
  });
}
mixins(C, [A, B]);

// 然后就可以使用被实现类里的函数方法了
let c = new C();
c.changeType(); // true

item.prototype里包含了constructor、定义在类里的函数(class 中直接定义的函数将会被添加到 prototype 上)等。

class Test {
  num: string;
  add() {}
}
Object.getOwnPropertyNames(item.prototype).forEach((name) => {
  console.log(name); // constructor add
});
泛型

可以理解为是动态类型,在定义函数时不明确类型,而是采用泛型(占位符),在使用时泛型会自动推断出相应类型。它主要是为了解决一些具有相同逻辑的函数但传入参数不同,而导致的代码逻辑重复性高的问题。

基本使用

函数泛型

function fun1<T>(a: T, b: T): Array<T> {
  // <>定义泛型,里面可以随意
  return [a, b];
}

fun1(1, 2); // fun1<number>(1,2) 写全是这样,但是ts能类型推断出来,所以可以不用写完整
fun1("1", "2");
fun1(false, true);

type 泛型

type A<T> = string | number | T;
let a: A<null> = null;

interface 泛型

interface B<T> {
  msg: T;
}

let data: B<number> = {
  msg: 1,
};

联合泛型

function add<T, K>(a: T, b: K): Array<T | K> {
  return [a, b];
}
add(1, false);

泛型也可以设置默认值:

function add<T = number, K = number>(a: T, b: K): Array<T | K> {
  return [a, b];
}

应用

调用接口时使用泛型:

const axios = {
  get<T>(url: string): Promise<T> {
    return new Promise((resolve, reject) => {
      let xhr: XMLHttpRequest = new XMLHttpRequest();
      xhr.open("GET", url);
      xhr.onreadystatechange = () => {
        if (xhr.readyState == 4 && xhr.status == 200) {
          resolve(JSON.parse(xhr.responseText));
        }
      };
      xhr.send(null);
    });
  },
};
interface Data {
  message: string;
  code: number;
}
axios.get<Data>("./data.json").then((res) => {
  console.log(res); // res能访问Data里定义的属性
});

泛型约束(extends,内有 keyof)

使用泛型约束来控制泛型的类型范围。通过再泛型的后面跟一个extends再跟一个约束的泛型类型来进行约束:

function add<T extends number>(a: T, b: T) {
  return a + b;
}
add(1, 2); // 这样对于add()来说约束了参数相加的类型,避免了不能相加的类型
  • 约束传入参数具有length属性:

    interface Len {
      length: number;
    }
    function fun<T extends Len>(a: T) {
      a.length;
    }
    fun("dsadas");
    fun([1, 2, 3]);
    // fun(123) 没有length,不支持
    // fun(true) 没有length,不支持
    
  • 约束对象的 key:

    约束传入的值为某个对象的属性。

    let obj = {
      name: "张三",
      age: 18,
    };
    function ob<T extends object, K extends keyof T>(obj: T, key: K) {
      return obj[key]; // 如果不对K进行约束,则key会报错
    }
    ob(obj, "name");
    

    keyof:将对象类型里的 key 推断成联合类型。

    type Key = keyof typeof obj; // type Key = "name" | "age"
    
  • 对对象类型里的 key(属性)添加可选 符号:

    interface Data {
      name: string;
      age: number;
      sex: string;
    }
    type Options<T extends object> = {
      [Key in keyof T]?: T[Key]; // 定义一个Key(任意)使用in操作符再联合类型T中循环
    };
    type B = Options<Data>;
    
数组的类型(\*)

数组定义

类型+方括号

let fibonacci: number[] = [1, 1, 2, 3, 5];
// 该数组对象里的所有元素的类型都要为number

数组的项中不允许出现其他的类型,数组的一些方法的参数也会根据数组在定义时约定的类型进行限制。

数组泛型

也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:

let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  • elemType:表示数组里元素的类型。

用接口表示数组

定义对象数组时可以使用 interface。

interface NumberArray {
  [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

interface A {
  name: string;
  age?: number;
}
let arr: A[] = [{ name: "张三" }, { naem: "李四" }];

NumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字。

一般常用来表示类数组。

多维数组

定义多维数组可以使用不同的方法“

  • 类型+方括号

    let arr: number[][] = [[1], [2], [3]];
    
  • 数组泛型

    let arr: Array<Array<number>> = [[1], [2], [3]];
    

是几维数组就套几层。

不同类型数值的数组

当数组里数值的类型有多种时,可以使用:

  • any

    let arr: any[] = [1, "asdfg", false, {}];
    
  • 元组类型

    使用元组类型将其中的值的类型对应起来。

    let arr: [number, string, boolean, {}] = [1, "asdfg", false, {}];
    

类数组

类数组(Array-like Object)不是数组类型,比如 arguments

function sum() {
  let args: number[] = arguments;
}

// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.

上例中,arguments 实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口:

function sum() {
  let args: {
    [index: number]: number;
    length: number;
    callee: Function;
  } = arguments;
}
  • 事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等。

    function sum() {
      let args: IArguments = arguments;
    }
    

    其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:

    interface IArguments {
      [index: number]: any;
      length: number;
      callee: Function;
    }
    
数据类型

布尔值

let isDone: boolean = false;

注意:使用构造函数Boolean创造的对象不是布尔值

数值

使用 number 定义数值类型:

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

编译结果:

var decLiteral = 6;
var hexLiteral = 0xf00d;
// ES6 中的二进制表示法
var binaryLiteral = 10;
// ES6 中的八进制表示法
var octalLiteral = 484;
var notANumber = NaN;
var infinityNumber = Infinity;

其中 0b10100o744ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。

字符串

使用 string 定义字符串类型:

let myName: string = "Tom";
let myAge: number = 25;

// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;

编译结果:

var myName = 'Tom';
var myAge = 25;
// 模板字符串
var sentence = "Hello, my name is " + myName + ".
I'll be " + (myAge + 1) + " years old next month.";

其中用一小撇来定义 ES6 中的模板字符串${expr} 用来在模板字符串中嵌入表达式。

空值

JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:

function alertName(): void {
  alert("My name is Tom");
}

声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefinednull(只在 –strictNullChecks 未指定时):

let unusable: void = undefined;

Null 和 Undefined

在 TypeScript 中,可以使用 nullundefined 来定义这两个原始数据类型:

let u: undefined = undefined;
let n: null = null;

void 的区别是,undefinednull 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给其他类型的变量,而void类型的变量不能赋值给其他类型的变量。

任意值

任意值(Any)用来表示允许赋值为任意类型。

let myFavoriteNumber: any = "seven";
myFavoriteNumber = 7;

如果是一个普通类型,在赋值过程中改变类型是不被允许的

let myFavoriteNumber: string = "seven";
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
  • 在任意值上访问任何属性都是允许的
  • 允许调用任何方法

未声明类型的变量会被识别为任意值类型

例:

let something;
let something: any;
// 两者等价