在命令行中查看ts编译器版本判断是否安装成功。

TypeScript 文件默认以 .ts 为后缀,TypeScript 是 JavaScript 的扩展,所以 TypeScript 代码要在 浏览器/Node 环境下运行,需要把 TypeScript 代码编译为 JavaScript 代码。

ts初体验

ts-node

这是一个基于Node.js的运行typescript的REPL环境,适用于 typescript@>=2.7

ts-node这个插件方便我们直接执行ts文件,不用手动执行ts编译后的js文件,很方便。

Github地址

全局安装: npm install -g ts-node

tsconfig.json

当使用 tsc 并不指定 要编译的ts文件 的情况下,会从当前运行命令所在的目录开始逐级向上查找 tsconfig.json 文件。

tsconfig.json 文件用来配置 tsc 的编译配置选项。

我们也可以通过 --project(-p) 来指定一个包含 tsconfig.json 文件的目录来进行编译。

"compilerOptions": { "module": "ES2015", "target": "ES5", "outDir": "./dist" "include": [ "./src/**/*"

compilerOptions字段定义了编译相关设置

  • module:指定编译后的代码要使用的模块化系统
  • target:指定编译后的代码对应的ECMAScript版本
  • outDir:指定编译后的代码文件输出目录
  • outFile:将输出文件合并成一个文件(合并的文件顺序为加载和依赖顺序)
  • include字段指定了要包含的编译文件目录,它的值是一个目录数组,使用glob模式

  • *匹配0或多个字符(不包括目录分隔符)
  • ?匹配一个任意字符(不包括目录分隔符)
  • **/递归匹配任意子目录
  • exclude:指定不要包含的编译文件目录,值也是一个目录数组,类似include,默认会排除 node_modules outDir 指定的目录。

    类型注解(类型声明、类型约束)

    JavaScript是动态语言,变量随时可以被赋予不同类型的值,变量值的类型只有在运行时才能决定。

    在编码(编译)阶段无法确定数据类型,会给程序在实际运行中带来极大的隐患,不利于编码过程中的错误排查。

    使用类型注解就能够在变量声明的时候确定变量存储的值的类型,用来约束变量或参数值的类型,这样在编码阶段就可以检查出可能出现的问题,避免把错误带到执行期间。

    语法:let变量:类型

    当变量接收了与定义的类型不符的数据会导致编译失败(警告)。

    官方文档: https://www.tslang.cn/docs/handbook/basic-types.html

    typescript中定义的类型有:

    数字、字符串、布尔值

    null、undefined

    数组、元组、枚举

    void、any、Never

    字符串&数字类型
    let a: string;
    a = 'asd';
    // a = 1;
    let b: string = 'test';
    let c: String = 'test123';    // 可以把基本数据类型赋值给对应的包装对象类型
    let d: String = new String('test456');
    // let e: string = new String('test789');    // 不可以把包装对象类型赋值给基本类型
    let f: number;
    f  = 123;
    // f = '123';   // 报错
    

    string、number、boolean属于基本类型

    String、Number、Boolean:属于对象类型

  • 包装类型可以赋值给对应包装对象
  • 包装对象不可以赋值给对应基本类型
  • let s: String = 'some string...';		// 正确
    let s: string = new String('some string...');		// 错误
    

    TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:

    let list: number[] = [1, 2, 3];
    

    第二种方式是使用数组泛型,Array<元素类型>

    let list: Array<number> = [1, 2, 3];
     * 定义数组的方式有2种,第一种可以在元素后面接上[],表示由此类型元素组成的一个数组
     * 第二种方式就是使用数组泛型,Array<元素类型>
    let list: number[] = [1,2,3];
    console.log(list);
    // console.log(list.push('4'));    // 直接报错 不能把字符串类型的数据添加到number类型的数组中
    // 数组泛型创建方式
    let list2: Array<string> = ['a','b','c'];
    console.log(list2);
    console.log(list2.push('1,2,3','456'));
    // console.log(list2.push(1,2,3));   // 直接报错 不能把number类型的数据添加到string类型的数组中
    console.log(list2);
    

    与数组类似,允许表示一个已知元素数量和类型的数组,各元素的类型不必相同,对于下标内的数据,数据顺序必须与声明中的类型一一对应。

    * 与数组类似,但是可以存放多种不同类型 let data1: [number, string, boolean]; // 注意:顺序要对应 data1 = [1,'123',true]

    多个类型中的一个,或的关系。

    let a: string | number;
    a = "m";
    a = 1;
    a = '1';
    // a = false;   // 报错,不能把boolean类型分配给string或者number类型
    console.log(a);
    

    使用枚举可以为一组数据赋予友好的名字,enum Color {Red, Green, Blue},默认情况下,元素编号从0开始,也可以手动编号enum Color {Red=1...}

    // let gender:number = 1;    // 1:男,2:女
    // if(gender==1) {   // 容易忘记1表示的是什么
    // }else {}
    enum Gender {Male, Female};  // enum Gender {Male=0, Female=1};
    if(Gender.Male) {
      console.log("男");
    }else {
      console.log("女")
    // enum Gender {Male=1, Female};   // Female会从2开始
    

    有的时候不一定需要强制使用类型声明,在某些情况下 TS 可以根据语境进行类型推导。

    变量初始化的时候TS 会根据变量初始化的时候赋予的值进行类型推断。

    上下文推断的时候TS 也会根据上下文进行类型的推断,比如在事件函数中,函数的第一个参数会根据当前绑定的事件类型推断处理事件对象。

    * 其它类型 // let a:undefined; // a = undefined; // 下面是可以的,可以把null赋值给其他类型,但是不能把其他类型的数据赋值给null // let a: number; // a = null; // let b: any; // b = false; // console.log(b) // 类型推导 // let c = 1; // ts会自动推导,c是number类型的 // c = 'm'; // 报错

    Part2内容

    上面的代码在页面中没有这个div元素的时候会报错,因为document.querySelector('div')在页面中没有div的时候会返回一个null值,然后给这个null值添加颜色就会报错,这种问题可以通过TS来解决。

    注意:在js中,如果一个方法的返回值是一个对象,那么该方法在没有返回值的情况下,接收的变量就会为null。

    TS类似于ESLINT,通过定义各种类型检测规则来约束代码,减少隐形的错误。

    // 函数声明写法
    function fn1(x: number, y: number): number {
      return x + y
    let result = fn1(1,2);
    

    函数表达式

    // 函数表达式
    let fn2 = function(x: number, y:number): number {
      return x+y;
    

    完整函数类型写法

    // 完整的函数类型写法
    let fn2: (x:number, y:number) => number = function(x: number, y:number): number {
      return x+y;
    // 根据类型推断可以简写
    let fn2: (x: number, y:number) => number = function(x,y) {
      return x+y;
    

    可选参数写法

    // 可选参数,没有返回值就使用viod关键字代替,可选参数使用?标识
    function fn3(x: number, y?: number) :void {};
    console.log(fn3(1));
    

    参数默认值写法

    // 参数默认值
    function fn3(x: number, y = 1): void {
      console.log(y);
    console.log(fn3(0));
    

    剩余参数写法

    // 剩余参数
    function createName(firstName: string, ...args: string[]){
      return firstName + " " + args.join(" ");
    let res = createName("Alex", "Bob", "Simth");
    console.log(res);
    

    允许我们在TS中给函数传递多种对应类型的参数,前提是你已经定义好了参数的类型。

    // function fn(x, y) {
    //   return x + y;
    // 如果在js中,我们是可以这么写的,因为没有类型检查,但是在TS中使用类型检查后推荐使用函数重载方式实现
    // console.log(fn(1, 2));  // 3
    // console.log(fn('a', 'b'));    // ab
    // 函数重载,允许我们在TS中给函数传递多种对应类型的参数
    function fn(x: number, y: number): number;
    function fn(x: string, y: string): string;
    function fn(x: any, y: any): any {
      return x + y;
    console.log(fn(1,2));   // 3
    console.log(fn('alex', 'zhang'));   // alexzhang
    // console.log(fn('haha', 123));    // 直接报错
    

    注意:在TS中能不使用any类型就不使用,因为使用any类型TS就不会对其做类型检测了,就没有任何意义了。

    因为普通函数中的 this 具有执行期绑定的特性,所以在 ts 中的this 在有的时候会指向隐式的指向类型 - any(并不是所有,比如事件函数)。

    我们可以通过 --noImplicitThis 选项来解决 this 隐式 any 类型的错误。

    我们可以在函数参数中提供一个显示的 this 参数,this 参数是一个假的参数,它出现在参数列表的最前面。

    * ts中默认情况下函数中的this默认指向 : any let obj = { a: 10, fn() { // 因为默认情况下,this是any类型,any类型ts不能提示有任何属性方法 // let document:any; // any的值,ts不能提示或者进行类型属性检测 // console.log(this.b); // 使用noImplicitThis选项可以取消默认this的any来这个设置 // this.a // obj.fn(); // ts会自动推导事件函数中的this // document.onclick = function() { // this let obj1 = { a: 1, fn(this: Element|Document) { // 在ts中函数的第一个this参数是用来设置this类型约束的 // 这个this是一个假参数,运行过程中是不存在,是给ts检测使用的 // console.log(this); document.onclick = obj1.fn; document.body.onclick = obj1.fn;

    ts会自动推导事件函数中的this!!!

    注意:TS是根据类型来做代码检测。

    与 ES2015 中的 class 类似,同时新增了很多实用特性,与 ES2015 不同,TS 中的成员属性可以提取到构造函数以外进行定义。

    通过修饰符可以对类中成员属性与成员方法进行访问控制,publicprotectedprivatereadonly

    参数属性:我们可以在参数中使用修饰符,它可以同时定义并初始化一个成员属性。

    class Person {
       * ts中的类,成员属性必须要声明后使用
       * ts中的类的成员属性不是在构造函数中声明的,是在class内,方法外声明
       * public
       *    公开的,所有的地方都能访问,属性和方法默认是public
       * protected
       *    受保护的,在类的内部和他的子类中才能访问
       * private
       *    私有的,只能在该对象(类)的内部才可以访问
      public username: string = '';
      // private username: string = '';
      // protected username: string = '';
      constructor(name: string) {
        this.username = name;
    class Student extends Person {
      say() {
        console.log(`${this.username}:哈哈哈`);
    let p1: Person = new Person('alex');
    p1.username = 'john'
    console.log(p1.username);
    

    TS 支持 getters/setters 来截取对对象成员的访问。

    第一个需求:我们不希望年龄被修改。

    解决方法:直接使用private关键字定义age成员属性即可,因为private定义的属性不能在外部访问,只能在类的内部访问。

    此时,需求变动,我们允许用户修改年龄,但是要在合理的访问内修改,不能随意修改,比如只能在0到150岁之间。

    解决方法:在Person内中定义getAgesetAge方法来实现,getAge方法中直接返回类中的age属性,setAge方法接收外部传递进来的参数,然后修改age属性的值达到修改的效果,哦,别忘了加一个判断,判断通过才修改。

    class Person {
      username: string = 'alexander';
      private _age: number = 21;
      getAge(): number {
        return this._age;
      setAge(age: number): void {
        if (age > 0 && age < 150) {
          this._age = age;
        }else {
          console.log("[-]验证未通过!");
    let p1: Person = new Person();
    // 需求:允许在外部获取和修改age的值,但是不希望被修改成非法值,比如1000岁
    console.log(p1);
    console.log(p1.getAge());
    console.log(p1.setAge(88));
    console.log(p1.getAge());
    

    使用TS的存取器来实现

    class Person {
      username: string = 'alexander';
      private _age: number = 21;
      // getAge(): number {
      //   return this._age;
      // setAge(age: number): void {
      //   if (age > 0 && age < 150) {
      //     this._age = age;
      //   }else {
      //     console.log("[-]验证未通过!");
      //   }
      // 存取器,这个age并不会作为方法,而是作为属性去访问,类似Vue中的computed计算属性
      get age(): number {
        return this._age;
      set age(age: number) {
        if (age > 0 && age < 150) {
          this._age = age;
    let p1: Person = new Person();
    // 需求:允许在外部获取和修改age的值,但是不希望被修改成非法值,比如1000岁
    console.log(p1.age)   // 21
    p1.age = 121;    // set age 121
    console.log(p1.age);  // 121
    p1.age = 123;    //set age error
    console.log(p1.age);    // 123
    

    TS官方的demo

    let passcode = "secret passcode";
    class Employee {
        private _fullName: string;
        get fullName(): string {
            return this._fullName;
        set fullName(newName: string) {
            if (passcode && passcode == "secret passcode") {
                this._fullName = newName;
            else {
                console.log("Error: Unauthorized update of employee!");
    let employee = new Employee();
    employee.fullName = "Bob Smith";
    if (employee.fullName) {
        alert(employee.fullName);
    

    注意下面几点:

    使用存取器的成员需要被private修饰。

    编译目标为 ES5+。

    只有 get 的存取器自动被推断为 readonly只读属性。

    没看官方文档之前,我还傻傻地使用p1.age(121)的方式尝试修改_age属性的值,始终不成功,看了文档之后发现我的写法是错的,应该用=号赋值的方式来写,总结一下,与其质疑框架、工具、库版本问题,坑多难用,不如多看官方文档,文档看不好,Bug少不了!:)

    类的一般成员属性和方法都属于实例对象的,也就是原型链上的,静态成员属于类(也就是构造函数)的,静态成员不需要实例化对象,直接通过类即可调用。

    通过下面的demo彻底理解

    // 单例模式demo
    // class Mysql {
    //   // 成员属性声明, 默认public
    //   host: string;
    //   port: number;
    //   username: string;
    //   password: string;
    //   dbname: string;
    //   constructor(host = '127.0.0.1', port = 3306, username='root', password='', dbname='') {
    //     this.host = host;
    //     this.port = port;
    //     this.username = username;
    //     this.password = password;
    //     this.dbname = dbname;
    //   }
    //   // 类方法
    //   query(){console.log("query data...")}
    //   insert(){console.log("insert data...")}
    //   update(){console.log("update data...")}
    // /**
    //  * 创建一个Mysql对象,通过这个对象来操作数据库
    //  * 如果我们不加以限制的话,这个Mysql是可以new出来多个对象的
    //  * 每一个Mysql都会占用资源(内存)
    //  * 
    //  * 为了解决这个问题,我们需要对创建Mysql连接做限制,如果存在则直接使用已有的连接,不存在
    //  * 则创建。
    //  */
    //  let db = new Mysql();
    //  db.query();
    //  db.insert();
    //  let db1 = new Mysql();
    //  db1.query();
    //  db1.insert();
     * 通过某种方式控制系统同时只有一个Mysql的对象在工作
    class Mysql {
      // 静态属性,不需要通过new出来的对象,直接是通过Mysql类来访问
      public static instance;
      host: string;
      port: number;
      username: string;
      password: string;
      dbname: string;
      private constructor(host = '127.0.0.1', port = 3306, username = 'root', password = '', dbname = '') {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        this.dbname = dbname;
      public static getInstance() {
        if (!Mysql.instance) {
          Mysql.instance = new Mysql();
        return Mysql.instance;
      query() { console.log("query data...") }
      insert() { console.log("insert data...") }
      update() { console.log("update data...") }
    // let db = new Mysql();
    console.log(Mysql.instance);
    let db = Mysql.getInstance();
    db.query();
    console.log(Mysql.instance);
    db.insert();
    db.update();
    
    class Person {
      // 在构造函数的参数中如果直接使用public等修饰符,则等同于同时创建了该属性
      constructor(public username: string, public age:number) {
        this.username = username;
        this.age = age;
    class Student extends Person {
       * 如果子类没有重写构造函数,则直接使用父类的
       * 如果子类重写了构造函数,则需要手动调用父类构造函数
       * super:关键字,表示父类
       constructor(username: string, age:number, public gender: string) {
        super(username, age);   // 执行父类构造函数
        this.gender = gender
    let s1 = new Student('alexander', 21, '男');
    console.log(s1);
    

    类是对具有相同特性的对象的抽象,抽象类是对具有相同特性的类的抽象,当派生类(子类)具有的相同的方法但有不同实现的时候,可以定义抽象类并定义抽象方法。

    抽象方法只定义结构不定义实现,拥有抽象方法的类必须是抽象类,但是抽象类不一定拥有抽象方法,抽象类中也可以包含有具体细节的方法,abstract 关键字可以与 修饰符一起使用,继承了抽象类的子类必须实现了所有抽象方法才能被实例化,否则该子类也必须声明为抽象的。

    第一次接触这个概念感觉有亿点点抽象,还是直接看代码。

    abstract class Person {   // 抽象类是不能实例化的
      username: string;
      constructor(username: string) {
        this.username = username;
      say() {
        console.log("哈哈哈哈哈");
        虽然子类都会有这样的特性,学习,但是子类的学习具体过程不一样,所以在父类确定不了study方法
        的具体实现,父类只能有抽象的约定,接收什么参数,返回什么内容。
        如果一个类中有抽象的方法了,那么这个类2也必须是抽象的。
      abstract study():void // 抽象方法是没有具体代码的
    class Student extends Person {
      study() {
        console.log("学生有学生的学习方法 - 需要老师教授")
    class Teacher extends Person {
      study() {
        console.log("老师的学习方法 - 自学");
    // 如果一个类继承了抽象的父类,就必须实现所有抽象方法,否则这个子类也必须是一个抽象类
    abstract class P extends Person {}
    let s1 = new Student('alex');
    console.log(s1.say());
    console.log(s1.study());
    

    看完代码就理解了上面的话了。

    Part3内容

    接口(interface)

    Hello,Interface

    接口为我们提供一种方式来定义某种结构,ts按照这种结构来检测数据。

    下面看一个基础的例子。

    * interface * 为我们提供一种方式来定义某种结构,ts按照这种结构来检测数据 * 写法: * interface 接口名称 { * // ...接口规则 * } * 接口中定义的规则只有抽象描述,不能有具体的值或实现代码 * 对象抽象 => 类 (把对象相似的部分提取出来通过这个类去描述对象) * 类抽象 => 抽象类 (如果一个类中有一个抽象方法没有实现,那么这个类就是抽象类) * 抽象类 => 接口 (如果一个抽象类中的所有成员都是抽象的,这个类就是接口) // 定义一个名为Options接口,可以把接口看成是一个对象,但是不完全是,有些细节不一样。 interface Options { // width: number = 1, // 接口中的代码不能有值 width: number, height: number function fn(opts: Options) { console.log(opts); // fn(); // 报错,没有传入参数 // fn({}); // 传入的参数类型不对,ts会按照接口中定义的数据去检测 // fn({width: 300}); // 缺少height属性 // 细节:类型检测只检测必须的属性是否存在,不会按照顺序进行检测,是无序的 fn({ width: 200, height: 150 }); // 正确

    ts中的interface接口是用来定义规则的,这个规则是给ts用来做数据检测的,上面的fn函数就是用了Options接口的规则来做数据检验,规则中定义了number类型的width属性和number类型的height属性,给fn函数传参的时候就得按照这种规则传,否则就报错。

  • ts中的接口是用来做类型检验的,必须严格按照接口中定义的规则来实现
  • ts的类型检测只检测必须的属性是否存在,不会按照顺序进行检测,是无序的
  • 可选参数和只读属性

    * 如果规则中有些是可选的,那么通过 ? 标识。 * 只读属性通过 readonly 关键字标识 interface Options { width: number, height: number, color?: string, readonly opcity: number function fn(params: Options) { console.log(params); // params.opcity = 1.2 // 报错,只读属性不能重新赋值 width: 200, height: 250, opcity: 0.5
  • 可选参数通过?标识
  • 只读属性通过readonly关键词标识
  • 如果我们希望检测不要这么复杂 -如果我们希望某些时候,只要包含其中一些规则即可 - 通过可选参数 ? 方式实现 - 通过 as 断言 可以传少,不能传多 - 通过变量转换 可以传多,不能传少 interface Options { width: number, height: number, color: string function fn(params: Options) { console.log(params); // fn({ // width: 200, // height: 300 // } as Options); // 明确告诉ts我传入的就是Options,让ts绕开检测 // 先赋值给一个变量,也可以绕开规则检测,原因是ts没有对obj这个变量做类型检测,但是这种方式只能传多不能传少 let obj = { height: 200, width: 100, color: 'red', a: 1, b: 2, fn(obj); 希望规则是:一组由数字进行key命名的对象,比如下标为0,1,2,3,4这样的 我们可以使用索引签名 为数据定义一组具有某种特性的key的数据 索引key的类型只能是 number和string两种 // 需求类似下面这种结构 // interface Options { // 0: string, // 1: string, // 2: string // 定义了一组规则,key是number类型,使用中括号包裹[变量名: 类型], value是any任意类型 interface Options { // key是number,value是any类型的 // [attr: number]: any, // key是string的话,会同时支持string和number类型 [attr: string]: any, length: number function fn(params: Options) { console.log(params) // 这种结构类似调用document.querySelectorAll('div')返回的NodeList结构 0: 100, a: 'haha', length: 1

    函数类型接口

    通过接口的形式来定义函数。

    一个简单的函数类型接口定义。

    这个接口描述的是一个包含fn,并且值的类型为函数的结构体,并不是描述函数结构; 注意描述的是一个包含函数的对象结构 interface Options { fn: Function let o: Options = { fn: function () {console.log("I Am Function...")} o.fn(); // I Am Function...

    注意:我们不能把一个函数随便赋值给事件,因为事件回调函数的参数类型是Event,这是JS规定的。

    // 完整描述函数结构写法
    // let fn: (x: number, y: number) => number = function (x: number, y: number): number {
    //   return x + y
      定义一个事件函数,那么这个函数必须得有一定的规则;
      我们不能随随便便的把一个函数赋值给事件
    // document.onclick = fn;    // 报错,因为fn函数定义的参数类型是number,而事件触发后的事件对象是Event类型
    function fn(x: Event) {
    document.onclick = fn;    // 正确写法
    

    再来看一个函数类型接口的例子。

    // 我们可以使用 interface 来约定函数的结构
    // 定义的是函数类型接口,定义了一个x,y参数和返回值都为number类型的函数,根据这种规则进行检测
    interface IFn {
      (x: number, y: number): number
    let fn: IFn = function(x: number, y: number): number {return x + y};
    // 定义了一个接收一个MouseEvent类型参数的函数结构
    // 其实函数接口就是定义一种函数规则,然后后面复用这种规则
    interface MouseEventCallBack {
      (e: MouseEvent): any
    let fn: MouseEventCallBack = function(a: MouseEvent){
    document.onclick = fn
    

    定义一个名为MouseEventCallBack的接口,接收的参数名为e,类型是MouseEvent类型,返回值是any。

    fn函数按照MouseEventCallBack接口定义的规则做检测,函数中参数a是一个MouseEvent类型,因此ts检测通过,可以将fn函数赋值给事件。

    再来举一个例子。

    定义一个ResponseCallBack函数接口 函数的第一个参数名为res,Response类型,返回值是any类型的 interface ResponseCallBack { (res: Response): any function todo(callback: ResponseCallBack) { callback(new Response) todo(function(res: Response){

    函数todo接收一个callback参数作为回调函数并且按照ResponseCallBack函数接口做规则检测,检测的规则是接收一个名为res的参数,类型是Response类型,返回值是any类型,调用todo函数,传入一个res参数名,类型为Response,然后调用callback并传入一个Response对象。

    部分代码需要拿到浏览器环境跑,直接用node跑可能会报错,例如这个Response。

    再来看一个fetch的例子。

    // fetch返回的是一个Promise对象,then方法成功回调函数的参数是一个Response类型对象
    fetch('url').then( (a: string) => {
      a.indexOf('');    // ts会检测到a不是一个string类型的,根本没有indexOf方法,这时就会报错
    fetch('url').then((a: Response) => {
      // a.indexOf('');    // Response类型没有这个indexOf方法,这里就会报错
      return a.json();  // Response对象是有json方法的,所以检测通过,可以参考MDN文档的Response对象
    

    不要尝试去欺骗ts编译器,没有意义,ts并不会按照你写的a:string就将a按照string类型进行检测,因为then方法的回调函数参数就是一个Response对象,这是JS的规则。

    再来看一个ajax的例子。

    // 再看一个ajax的例子
    interface AjaxData {
      code: number,
      data: any
    interface AjaxCallBack {
      (res: AjaxData): any
    // ajax函数的参数是一个callback回调函数,类型是AjaxCallBack,AjaxCallBack的参数类型是AjaxData类型
    function ajax(callback: AjaxCallBack){
      callback({
        code: 1,
        data: [
          {goods_name: 'IKBC C87红轴', goods_id: 1, goods_price: 299.99, goods_color: 'Black'},
          {goods_name: 'IKBC C87青轴', goods_id: 2, goods_price: 289.99, goods_color: 'Green'},
          {goods_name: 'IKBC C87黑轴', goods_id: 3, goods_price: 279.99, goods_color: 'White'},
        // message: '123'   // AjaxData没有定义message属性所以为报错
      // 可以通过之前学习到的变量赋值的方式绕过检测
      // let obj = {
      //   code: 1,
      //   data: [],
      //   message: '123'
      // callback(obj)
    ajax(function(x: AjaxData){
      console.log(x);
    // 总结:函数接口其实就是定义某种函数结构,接收的参数是什么类型,返回值是什么类型,做严格约束
    // typescript倒逼我们学习了更多javascript语言本身的东西,例如各种规则
    

    总结:函数接口其实就是定义某种函数结构,接收的参数是什么类型,返回值是什么类型,做严格约束。

    第一次看这个的时候一脸懵逼=>万脸懵逼=>递归懵逼,只能怪自己太蠢,后面静下心来反复看,反复理解,反复写就明白了,slow is fast

    类类型接口

    * 类接口 * 使用接口让某个类去符合某种契约 * 类可以通过 implements 关键字去实现某个接口 * - implements 某个接口的类必须实现这个接口中确定所有的内容 * - 一个类只能有一个父类,但是可以implements多个接口,多个接口使用逗号分隔 interface ISuper { fly(): void; class Man { constructor(public name: string) { class SuperMan extends Man implements ISuper { fly() { console.log('起飞'); class Cat { class SuperCat extends Cat implements ISuper { fly() { console.log('起飞'); let kimoo = new SuperMan('Kimoo'); // kimoo

    封装http案例

    function http(params) {
      return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open(params.method, params.url, params.isAsync);
        xhr.onload = function () {
          resolve(JSON.parse(xhr.responseText));
        xhr.onerror = function () {
          reject({
            code: xhr.response.code,
            message: '出错了!'
        xhr.send();
    // 期待的使用方式
    // http('url').then( data => {} )
    http({
      method: 'get',
      url: 'http://www.baidu.com/',
      isAsync: true
      问题:如果将method改成methods,因为没有类型检测,你很难在代码里面看出来
      必须在运行之后才能看出来。
      这个时候如果使用ts来实现就会好很多。
    
    interface HttpOptions {
      method: string,
      url: string,
      isAsync: true
    interface HttpResponseData {
      code: number,
      data: any
    function http(options: HttpOptions) {
      // 默认值处理
      let params: HttpOptions = Object.assign({
        method: 'get',
        url: '',
        isAsync: true
      }, options)
      return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open(params.method, params.url, params.isAsync);
        xhr.onload = function () {
          let data: HttpResponseData = JSON.parse(xhr.responseText);
          resolve(data);
        xhr.onerror = function () {
          reject({
            code: 0,
            data: []
        xhr.send();
    // 期待的使用方式
    // http('url').then( data => {} )
    http({
      method: 'get',
      // methods: 'get',   // 报错,因为HttpOptions接口中没有定义methods属性,ts会帮助我们在程序运行之前检测代码是否错误
      url: 'https://www.baidu.com/',
      isAsync: true
    }).then( res => {
      console.log(res);
    

    安装TS:npm install -g typescript

    安装ts-node编译器:npm install -g ts-node

    tsconfig.json作用是什么?

    是用来对typescript编译器进行配置的,比如配置编译模式、模块化系统、输出目录、编译文件目录、包含文件的层级设置、空值检查、取消this默认指向any等等。。。

    类型系统,常用有哪些类型?

    格式:let a: 类型 = 123

  • string
  • number
  • boolean
  • array
  • tuple(元组)
  • 联合类型:可以使用定义好的类型中任意一种类型进行赋值

    类型推导:typescript编译器会根据初始化变量或上下文来做类型推导,例如一个函数的2个参数都是定义为number类型的,那么返回值根据类型推导也是number类型,此时可以省略不写。

    函数创建方式:

  • 函数声明 function fn1(x: number, y: number): number {return x+y};
  • 函数表达式 let fn2 = function(x: number, y: number):number {return x+y};
  • 完整函数写法:

    let fn2: (x:number, y:number) => number = function(x:number, y:number):number{return x+y};

    根据类型推断简写:

    let fn2: (x:number, y:number) => number = function(x,y){return x+y};

    可选参数写法:使用?标识,如果没有返回值使用void代替

    function fn3(x: number, y?:number):void{console.log(y)}

    参数默认值写法:和javascript一样使用=号

    function fn3(x: number, y=1): void {console.log(y)}

    剩余参数写法:

    function fn3(x: number, ...args: string[]){ return x + " " + args.join(" "); }

    函数重载:允许我们在TS中给函数传递多种对应类型的参数,前提是你已经定义好了参数的类型。

    function fn(x: number, y: number);

    function fn(x: string, y: string);

    function fn(x: any, y: any): any {return x + y}

    TS函数中的this:

    在ts中,函数默认的this指向any(并不是所有,比如事件函数除外),这会导致ts的类型系统不会检测this,因为ts不会检测any类型,所以我们需要配置noImplicitThis:true来解决this默认指向any的问题。

    在ts中函数的第一个this参数是用来设置this类型约束的,约定this指向的是什么类型。

    类是对有相同特性对象的抽象。

    修饰符

    作用是为了对成员属性和方法进行访问控制

  • public:ts中类的默认修饰符,允许类中的属性和方法公开访问
  • private:私有修饰符,只允许属性在类的内部进行访问
  • protected:受保护修饰符,只允许属性在类的内部和它的子类中访问
  • readonly:将属性设置为只读的, 只读属性必须在声明时或构造函数里被初始化
  • 截取对对象成员的访问,get/set,注意使用get/set定义的方法在使用的时候不需要加括号,当作属性使用即可,参考修改年龄代码。

    静态成员:

    不需要通过实例化的对象,直接通过类进行调用,适用于同时只能有一个对象在工作的情况,参考单例模式的Mysql连接代码。

    类似ES6,使用extends关键字进行继承,如果子类没有重写构造函数,则直接使用父类的,如果重写了构造函数,则需要手动调用父类构造函数,使用super()关键字调用。

    抽象类是对具有相同特性的类的抽象,当子类具有的相同的方法但有不同实现的时候,可以定义抽象类并定义抽象方法;

    参考学生学习方法和老师学习方法的代码。

    源码在这里