TypeScript 基础

学习文档:https://www.runoob.com/typescript/ts-tutorial.html

1.概述

  • 1.什么是 TypeScript (TS)
    • TypeScript,简称 TS
    • TS和JS之间的关系,其实就是 Less/Sass 和 CSS 之间的关系
    • 就像Less/Sass是对CSS进行扩展一样, TS也是对JS进行扩展
    • 就像Less/Sass最终会转换成CSS一样, 我们编写好的TS代码最终也会换成JS
  • 2.为什么需要 TypeScript
    • 因为JavaScript是弱类型, 很多错误只有在运行时才会被发现
    • 而TypeScript是强类型, 它提供了一套静态检测机制, 可以帮助我们在编译时就发现错误
  • 3.TypeScript 特点
    • 支持最新的JavaScript新特性
    • 支持代码静态检查
    • 支持诸如C,C++,Java,Go等后端语言中的特性
    • 枚举、泛型、类型转换、命名空间、声明文件、类、接口等
/*
let val:number;
val = 123;
val = "123";
val = true;
val = [1, 3, 5];
 */
function test(a, b) {
    return a.length + b;
}
// let res = test([1, 3, 5], 10);
var res = test(1, 10);
console.log(res);    
  • 4.简单使用
    • 4.1 安装 typescript
      • npm install -g  typescript
    • 4.2 编写 test.ts
// 注意点: 由于TS并不是一门新的语言, 而是对JS的扩展, 所以我们可以在TS文件中直接编写JS代码
//         TS支持类型注解, 我们可以通过类型注解来告诉系统, 变量中将来只能存储什么类型的数据

function test(a:any[], b:number) { // 期望a是一个数组, b是一个数值
    return a.length + b;
}

let res = test([1, 3, 5], 10);
// let res = test(1, 10);          // 报错
console.log(res);    
  • 4.3 编译 test.ts
    • tsc test.ts
  • 4.4 编译后,便会生成一个 test.js 文件
  • 4.5 执行 test.js
    • node test.js

    

2.数据类型

  • number       数值类型
  • boolean      布尔类型
  • string          字符串类型
/* TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用*/
// 数值类型 number
let val1:number; // 定义了一个名称叫做val1的变量, 这个变量中将来只能存储数值类型的数据
val1 = 123;
// val1 = "123"; // 会报错
// 注意点: 其它的用法和JS一样
// val1 = 0x11;
// val1 = 0o11;
// val1 = 0b11;
console.log(val1);

// 布尔类型 boolean
let val2:boolean;
val2 = true;
// val2 = 1; // 会报错
console.log(val2);

// 字符串类型 string
let val3:string;
val3 = "123";
val3 = `val1=${val1}, val2==${val2}`;
console.log(val3);
  • 数组类型
    • Array<number                数值类型的数组
    • string[ ]                               字符串类型的数组
    • (number | string)[ ]             联合类型(数值或字符串)的数组
    • any[ ]                                  任意类型的数组
  • 元组类型
    • TS中的元组类型,其实就是数组类型的扩展
    • 元组用于保存,定长定数据类型的数据
    • 比如:[string, number, boolean]
// 数组类型
// 方式一
// 需求: 要求定义一个数组, 这个数组中将来只能存储数值类型的数据
let arr1:Array<number>; // 表示定义了一个名称叫做arr1的数组, 这个数组中将来只能够存储数值类型的数据
arr1 = [1, 3, 5];
// arr1 = ['a', 3, 5]; // 报错
console.log(arr1);

// 方式二
// 需求: 要求定义一个数组, 这个数组中将来只能存储字符串类型的数据
let arr2:string[]; // 表示定义了一个名称叫做arr2的数组, 这个数组中将来只能够存储字符串类型的数据
arr2 = ['a', 'b', 'c'];
// arr2 = [1, 'b', 'c']; // 报错
console.log(arr2);

// 联合类型
let arr3:(number | string)[];// 表示定义了一个名称叫做arr3的数组, 这个数组中将来既可以存储数值类型的数据, 也可以存储字符串类型的数据
arr3 = [1, 'b', 2, 'c'];
// arr3 = [1, 'b', 2, 'c', false]; // 报错
console.log(arr3);

// 任意类型
let arr4:any[]; // 表示定义了一个名称叫做arr4的数组, 这个数组中将来可以存储任意类型的数据
arr4 = [1, 'b', false];
console.log(arr4);

// 元组类型
// TS中的元组类型其实就是数组类型的扩展
// 元组用于保存定长定数据类型的数据
let arr5:[string, number, boolean]; // 表示定义了一个名称叫做arr5的元组, 这个元组中将来可以存储3个元素, 第一个元素必须是字符串类型, 第二个元素必须是数字类型, 第三个元素必须是布尔类型
arr5 = ['a', 1, true];
// arr5 = ['a', 1, true, false]; // 超过指定的长度会报错
arr5 = ['a', 1, true];
console.log(arr5);
  • 枚举类型
    • 枚举类型,是TS为JS扩展的一种类型, 在原生的JS中是没有枚举类型的(enum
    • 枚举用于表示,固定的几个取值
    • 例如: 一年只有四季、人的性别只能是男或者女
enum Gender{ // 定义了一个名称叫做Gender的枚举类型, 这个枚举类型的取值有两个, 分别是Male和Femal
    Male,
    Femal
}
let val:Gender; // 定义了一个名称叫做val的变量, 这个变量中只能保存Male或者Femal
val = Gender.Male;
val = Gender.Femal;
// val = 'nan'; // 报错
// val  = false;// 报错
// 注意点: TS中的枚举底层实现的本质其实就是数值类型, 所以赋值一个数值不会报错
// val = 666; // 不会报错
// console.log(Gender.Male); // 0
// console.log(Gender.Femal);// 1

// 注意点: TS中的枚举类型的取值, 默认是从上至下从0开始递增的
//         虽然默认是从0开始递增的, 但是我们也可以手动的指定枚举的取值的值
// 注意点: 如果手动指定了前面枚举值的取值, 那么后面枚举值的取值会根据前面的值来递增
// console.log(Gender.Male); // 6
// console.log(Gender.Femal);// 7

// 注意点: 如果手动指定了后面枚举值的取值, 那么前面枚举值的取值不会受到影响
// console.log(Gender.Male); // 0
// console.log(Gender.Femal);// 6

// 注意点: 我们还可以同时修改多个枚举值的取值, 如果同时修改了多个, 那么修改的是什么最后就是什么
// console.log(Gender.Male); // 8
// console.log(Gender.Femal);// 6

// 我们可以通过枚举值拿到它对应的数字
console.log(Gender.Male); // 0
// 我们还可以通过它对应的数据拿到它的枚举值
console.log(Gender[0]); // Male

// 探究底层实现原理
/*
var Gender;
(function (Gender) {
 // Gender[key] = value;
    Gender[Gender["Male"] = 0] = "Male";
    Gender[Gender["Femal"] = 1] = "Femal";
})(Gender || (Gender = {}));

let Gender = {};
Gender["Male"] = 0;
Gender[0] = "Male";
Gender["Femal"] = 1;
Gender[1] = "Femal";
* */
  • any 类型
    • any 表示任意类型, 当我们不清楚某个值的具体类型的时候,我们就可以使用 any
    • 一般用于定义一些通用性比较强的变量, 或者用于保存从其它框架中获取的不确定          类型的值
    • 在TS中,任何数据类型的值,都可以负责给any类型
  • void 类型
    • void 与any正好相反, 表示没有任何类型, 一般用于函数返回值
    • 在TS中,只有 null undefined,可以赋值给 void 类型
// any类型
// let value:any; // 定义了一个可以保存任意类型数据的变量
// value = 123;
// value = "abc";
// value = true;
// value = [1, 3, 5];

// void类型
function test():void {
    console.log("hello world");
}
test();

let value:void; // 定义了一个不可以保存任意类型数据的变量, 只能保存null和undefined
// value = 123; // 报错
// value = "abc";// 报错
// value = true;// 报错
// 注意点: null和undefined是所有类型的子类型, 所以我们可以将null和undefined赋值给任意类型
// value = null; // 不会报错
value = undefined;// 不会报错
  • never 类型
    • never 表示的是,那些永不存在的值的类型
    • 一般用于抛出异常,或根本不可能有返回值的函数
  • object 类型
    • object  表示一个对象
// never类型
// function demo():never {
//     throw new Error('报错了');
// }
// demo();

// function demo2():never {
//     while (true){}
// }
// demo2();

// object类型
let obj:object; // 定义了一个只能保存对象的变量
// obj = 1;
// obj = "123";
// obj = true;
obj = {name:'lnj', age:33};
console.log(obj);
  • 类型断言(类型转换)
    • TS中的类型断言和其它编程语言的类型转换很像, 可以将一种类型强制转换成另外一种  类型
    • 类型断言就是告诉编译器, 你不要帮我们检查了, 相信我,我知道自己在干什么。
  • 例如:我们拿到了一个any类型的变量, 但是我们明确的知道这个变量中保存的是字符串类型
  • 此时我们就可以通过类型断言告诉编译器, 这个变量是一个字符串类型
  • 此时我们就可以通过类型断言将any类型转换成string类型, 使用字符串类型中相关的方法了
    • <string>str
    • str as string
let str:any = 'it666';
// 方式一
// let len = (<string>str).length;
// 方式二
// 注意点: 在企业开发中推荐使用as来进行类型转换(类型断言)
//         因为第一种方式有兼容性问题, 在使用到了JSX的时候兼容性不是很好
let len = (str as string).length;
console.log(len);

3.接口类型

  • 接口类型和 number,string,boolean,enum 这些数据类型一样
  • 接口也是一种类型, 也是用来约束使用者的(interface
// 定义一个接口类型
interface FullName{
    firstName:string
    lastName:string
}
let obj = {
    firstName:'Jonathan',
    lastName:'Lee'
    // lastName:18
};
// 需求: 要求定义一个函数输出一个人完整的姓名, 这个人的姓必须是字符串, 这个人的名也必须是一个字符
function say({firstName, lastName}:FullName):void {
    console.log(`我的姓名是:${firstName}_${lastName}`);
}
say(obj);
  • 1.可选属性  ?
  • 2.索引签名  [propName:string]:any 
// 定义一个接口
interface FullName{
    firstName:string
    lastName:string
    middleName?:string       // 声明可选参数
    [propName:string]:any    // 索引签名
}
// 需求: 如果传递了middleName就输出完整名称, 如果没有传递middleName, 那么就输出firstName和lastName
function say({firstName, lastName, middleName}:FullName):void {
    // console.log(`我的姓名是:${firstName}_${lastName}`);
    if(middleName){
        console.log(`我的姓名是:${firstName}_${middleName}_${lastName}`);
    }else{
        console.log(`我的姓名是:${firstName}_${lastName}`);
    }
}
// 注意点: 如果使用接口来限定了变量或者形参, 那么在给变量或者形参赋值的时候
// 赋予的值就必须和接口限定的一模一样才可以, 多一个或者少一个都不行

// say({firstName:'Jonathan'});
// say({firstName:'Jonathan', lastName:'Lee', middleName:"666"});

// 注意点: 但是在企业开发中可以多一个也可能少一个
// 少一个或多个怎么做? 可选属性
// say({firstName:'Jonathan', lastName:'Lee', middleName:"666"});
// say({firstName:'Jonathan', lastName:'Lee'});
// 多一个或者多多个怎么做? 如果绕开TS检查
// 方式一: 使用类型断言
// say({firstName:'Jonathan', lastName:'Lee', middleName:"666", abc:'abc'} as FullName);
// 方式二: 使用变量
// let obj = {firstName:'Jonathan', lastName:'Lee', middleName:"666", abc:'abc'};
// say(obj);
// 方式三: 使用索引签名
say({firstName:'Jonathan', lastName:'Lee', middleName:"666", abc:'abc', 123:123, def:"def"});
  • 索引签名:用于描述那些“通过索引得到”的类型,比如:数组 arr[10] 或 对象 obj["key"]
  // 定义接口
  interface FullName{
    [propName:string]:string        //索引签名
  }
  // 限定对象
  let obj:FullName = {
    // 注意点: 只要key和value满足索引签名的限定即可, 无论有多少个都无所谓
    firstName:'Jonathan',
    lastName:'Lee',
    // middleName:false   // 报错
    // false: '666'       // 无论key是什么类型最终都会自动转换成字符串类型, 所以没有报错
  }
  // 定义接口
  interface stringArray {
    [propName:number]:string        //索引签名
  }
  // 限定数组
  // let arr:stringArray = {
  //   0:'a',
  //   1:'b',
  //   2:'c'
  // }
  let arr:stringArray= ['a','b','c']
  console.log(arr[0]);
  console.log(arr[1]);
  console.log(arr[2]);
  • 3.只读属性:让对象属性,只能在对象刚刚创建的时候,修改其值readonly
  interface FullName {
    firstName:string,
    readonly lastName:string     //只读属性(readonly)
  }
  let myName:FullName = {
    firstName: 'Jonathan',
    lastName: 'Lee'
  }
  // myName.lastName = "Wang";   //此时再修改便会提示错误
  // console.log(myName);

  // TS内部对只对属性进行了扩展, 扩展出来了一个只读数组
  // let arr2:Array<string> = ['a', 'b', 'c'];
  let arr2:ReadonlyArray<string> = ['a', 'b', 'c'];       //只读数组
  // console.log(arr2[0]);
  // arr2[0] = '666';           //此时再修改便会提示错误
  // console.log(arr2[0]);
  • 4.函数接口
    • 我们除了可以通过接口来限定对象以外, 我们还可以使用接口来限定函数
// 接口函数
interface SumInterface {
    (a:number, b:number):number      // 参数:返回值
}
let sum:SumInterface = function (x:number, y:number):number {
    return x + y;
}
let res = sum(10, 20);
console.log(res);
  • 5.混合类型接口
    • 约定的内容中,既有对象属性, 又有函数
// 混合类型接口
// 约定的内容中既有对象属性, 又有函数
// 要求定义一个函数实现变量累加
/*
let count = 0; // 会污染全局空间
function demo() {
    count++;
    console.log(count);
}
demo();
demo();
demo();
 */
/*
let demo = (()=>{ // 使用闭包确实可以解决污染全局空间的问题, 但是对于初学者来说不太友好
    let count = 0;
    return ()=>{
        count++;
        console.log(count);
    }
})();
demo();
demo();
demo();
 */
// 在JS中函数的本质是什么? 就是一个对象
// let demo = function () {
//     demo.count++;
// }
// demo.count = 0;
// demo();
// demo();
// demo();
interface CountInterface {
    ():void
    count:number
}
let getCounter = (function ():CountInterface {
    /*
    CountInterface接口要求数据既要是一个没有参数没有返回值的函数
                              又要是一个拥有count属性的对象
    fn作为函数的时候符合接口中函数接口的限定 ():void
    fn作为对象的时候符合接口中对象属性的限定  count:number
    * */
    let fn = <CountInterface>function () {
        fn.count++;
        console.log(fn.count);
    }
    fn.count = 0;
    return fn;
})();
getCounter();
getCounter();
getCounter();
  • 6.接口继承(extends
// 接口的继承
// TS中的接口和JS中的类一样是可以继承的
interface LengthInterface {
    length:number
}
interface WidthInterface {
    width:number
}
interface HeightInterface {
    height:number
}
interface RectInterface extends LengthInterface,WidthInterface,HeightInterface {
    // length:number
    // width:number
    // height:number
    color:string
}
let rect:RectInterface = {
    length:10,
    width:20,
    height:30,
    color:'red'
}

4.函数

  • 1.函数的定义
  // 命名函数
  function say1(name:string):void{
    console.log(name);
  }
  // 匿名函数
  let say2 = function(name:string):void{
    console.log(name);
  }
  // 箭头函数
  let say3 = (name:string):void=>{
    console.log(name);
  }
  • 2.函数的完整格式
    • 在TS中函数的完整格式,应该是由函数的定义实现两个部分组成的
  // TS函数完整格式
  // // 1.定义函数
  // let AddFun:(a:number, b:number)=>number;
  // // 2.实现函数
  // AddFun = function(x:number, y:number):number{
  //   return x + y;
  // }
  // let res = AddFun(1,2);
  // console.log(res);

  // 一步到位写法
  // let AddFun:(a:number, b:number)=>number =
  //   function(x:number, y:number):number{
  //     return x + y;
  // }

  // 会根据函数的定义,自动推导对应的数据类型
  let AddFun:(a:number, b:number)=>number =
    function(x, y){
      return x + y;
  }
  let res = AddFun(1,2);
  console.log(res);
  • 3.函数的声明(type
  // 1.type:声明函数
  type AddFun = (a:number, b:number)=>number;
  // 2.实现函数
  let add:AddFun = function(x, y){
    return x + y;
  }
  let res = add(30, 20);
  console.log(res);
  • 4.函数的重载
    • 函数的重载,就是同名的函数可以根据不同的参数实现不同的功能
/*
function getArray(x:number):number[] {
    let arr = [];
    for(let i = 0; i <= x; i++){
        arr.push(i);
    }
    return arr;
}
function getArray(str:string):string[] {
    return str.split('');
}
 */
// 定义函数的重载
function getArray(x:number):number[];
function getArray(str:string):string[];
// 实现函数的重载
function getArray(value:any):any[] {
    if(typeof value === 'string'){
        return value.split('');
    }else{
        let arr = [];
        for(let i = 0; i <= value; i++){
            arr.push(i);
        }
        return arr;
    }
}
// let res = getArray(10);
let res = getArray('www.it666.com');
console.log(res);
  • 5.函数的参数
    • 5.1 可选参数
  // 可选参数
  // 需求: 要求定义一个函数,可以实现2个数或者3个数的加法
  // function add(x:number, y:number, z?:number):number{
  //   return x +y +(z ? z : 0)
  // }
  // // let res = add(10, 20);
  // let res = add(10, 20, 30);
  // console.log(res);

  // 可选参数可以配合函数重载一起使用, 这样可以让函数重载变得更加强大
  // // 1.定义函数重载
  // function add(x:number, y:number):number;
  // function add(x:number, y:number, z:number):number;
  // // 2.实现函数重载
  // function add(x:number, y:number, z?:number){
  //   return  x +y +(z ? z : 0);
  // }
  // let res = add(10, 20, 30);
  // console.log(res);

  // 多个可选参数
  function add(x:number, y?:number, z?:number):number {
  // function add(x:number, y?:number, z:number):number {   // 可选参数后面只能跟可选参数(可选参数只能放在最后面)
    return x + (y ? y : 0) + (z ? z : 0);
  }
  let res = add(10);   // 可选参数可以是一个或多个
  console.log(res);
  • 5.2 默认参数
function add(x:number, y:number=10):number {
    return x + y;
}
// let res = add(10);
let res = add(10, 30);
console.log(res);
  • 5.3 剩余参数
function add(x:number, ...ags:number[]) {
    console.log(x);
    console.log(ags);
}
add(10, 20, 30, 40, 50)

5.泛型

  • 1.什么是泛型?
    • 在编写代码的时候,我们既要考虑代码的健壮性, 又要考虑代码的灵活性和可重用性
    • 通过TS的静态检测能让我们编写的代码变得更加健壮, 但是在变得健壮的同时却丢失了  灵活性和可重用性
    • 所以为了解决这个问题TS推出了泛型的概念
    • 通过泛型不仅可以让我们的代码变得更加健壮, 还能让我们的代码在变得健壮的同时保持灵活性和可重用性
  // 需求: 定义一个创建数组的方法, 可以创建出指定长度的数组, 并且可以用任意指定的内容填充这个数组
  // let getArray =(value:number, items:number = 5):number[]=>{
  //   return new Array(items).fill(value);
  // }
  // let arr = getArray(6,3);
  // // let arr = getArray("abc", 3); // 报错
  // console.log(arr);

  let getArray = (value:any, items:number = 5):any[]=>{
    return new Array(items).fill(value);
  };
  // let arr = getArray("abc", 3);
  let arr = getArray(6, 3);
  // console.log(arr);
  // 当前存储的问题:
  // 1.编写代码没有提示, 因为TS的静态检测不知道具体是什么类型
  // 2.哪怕代码写错了也不会报错, 因为TS的静态检测不知道具体是什么类型
  let res = arr.map(item=>item.length);  // ['abc', 'abc', 'abc'] => [3, 3, 3]
  console.log(res);
  // 泛型 <T>
  // 需求:要有代码提示, 如果写错了要在编译的时候报错
    let getArray = <T>(value:T, items:number = 5):T[]=>{
        return new Array(items).fill(value);
    };
    // let arr = getArray<string>('abc');
    // let arr = getArray<number>(6);
    // 注意点: 泛型具体的类型可以不指定
    //         如果没有指定, 那么就会根据我们传递的泛型参数自动推导出来
    let arr = getArray('abc');
    // let arr = getArray(6);
    let res = arr.map(item=>item.length);
    console.log(res);
  • 2.在泛型约束中,如何使用类型参数?
    • 一个泛型被另一个泛型约束, 就叫做泛型约束中使用类型参数
  // 需求: 要求指定的泛型类型,必须有Length属性才可以
  // 定义接口
  interface LengthInterface{
    length:number
  }
  // 泛型约束
  let getArray = <T extends LengthInterface>(value:T, items:number = 5):T[]=>{
    return new Array(items).fill(value);
  }
  let arr = getArray<string>('abc');
  // let arr = getArray<number>(6);
  let res = arr.map(item=>item.length);
  • 3.在泛型约束中,如何使用类型参数?
    • 一个泛型被另一个泛型约束, 就叫做泛型约束中使用类型参数
// 需求: 定义一个函数,用于根据指定的key获取对象的value
// interface KeyInterface{
//     [key:string]:any
// }
// K extends keyof T:表示K必须为T中的key
let getProps = <T, K extends keyof T>(obj:T, key:K):any=>{
    return obj[key];
}
let obj = {
    a:'a',
    b:'b'
}
// 代码不够健壮, 明明obj中没有c这个key但是却没有报错
// 所以需要在泛型约束中,使用类型参数
// let res = getProps(obj, "c");
let res = getProps(obj, "a");
console.log(res);

6.类

  • 1.类的定义
  class Person{
    // 和ES6区别, 需要先定义实例属性, 才能够使用实例属性
    name: string;
    age: number;
    constructor(name:string, age:number){
      this.name = name;
      this.age = age
    }
    say():void{
      console.log(`我的名称叫${this.name}, 我的年龄是${this.age}`);
    }
    // 静态属性
    static food:string;
    // 静态方法
    static eat():void{
      console.log(`我正在吃${this.food}`);
    }
  }
  let p = new Person('lnj', 18);
  p.say();
  Person.food = '蛋挞';
  Person.eat();

  // extends继承
  class Student extends Person{
    book: string;
    constructor(name:string, age:number, book:string){
      super(name, age);
      this.book = book;
    }
    say():void{
      console.log(`我是重写之后的say-${this.name}${this.age}${this.book}`);
    }
    static eat():void{
      console.log(`我是重写之后的eat-${this.food}`);
    }
  }
  let stu = new Student('zs', 18, '从零玩转');
  stu.say();
  Student.food = '冰淇淋';
  Student.eat();
  • 2.类属性修饰符
  • public (公开的)      
    • 如果使用public来修饰属性, 那么表示这个属性是公开
    • 可以在类的内部使用, 也可以在子类中使用, 也可以在外部使用
  • protected (受保护的) 
    • 如果使用protected来修饰属性, 那么表示这个属性是受保护
    • 可以在类的内部使用, 也可以在子类中使用
  • private (私有的)     
    • 如果使用private来修饰属性, 那么表示这个属性是私有
    • 可以在类的内部使用
class Person {
    public name:string; // 默认情况下就是public的
    protected age:number;
    private gender:string;
    constructor(name:string, age:number, gender:string){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    say():void{
        console.log(`name=${this.name},age=${this.age},gender=${this.gender}`);
    }
}
class Student extends Person{
    constructor(name:string, age:number, gender:string){
       super(name,age,gender);
    }
    say():void{
        // console.log(`name=${this.name}`);
        // console.log(`age=${this.age}`);
        // console.log(`gender=${this.gender}`);
    }
}
let p = new Person('lnj',34, 'male');
p.say();
// console.log(p.age);
// console.log(p.gender);

let stu = new Student('zs', 18, 'female');
stu.say();
// console.log(stu.age);
  • readonly (只读的)    
class Demo {
    readonly name:string;
    constructor(name:string){
        this.name = name;
    }
    static food:string;
}
let demo = new Demo('123');
console.log(demo.name);
// demo.name = 'abc';
console.log(demo.name);
  • 3.类方法修饰符
    • 和类属性修饰符一样
// 需求: 有一个基类, 所有的子类都需要继承于这个基类, 但是我们不希望别人能够通过基类来创建对象
class Person {
    name:string;
    age:number;
    gender:string;
    protected constructor(name:string, age:number, gender:string){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    say():void{
        console.log(`name=${this.name},age=${this.age},gender=${this.gender}`);
    }
}
class Student extends Person {
    constructor(name: string, age: number, gender: string) {
        super(name, age, gender);
    }
}
// 无法直接通过Person创建
// let p = new Person('lnj', 34, 'male');
// 可以通过子类Student创建
let stu = new Student('zs', 18, 'female');
  • 4.存取器
    • 通过 getters / setters 来截取,对 对象成员 的访问
  class Person{
    private _age:number = 0;
    // 存储数据
    set age(val:number){
      console.log('进入了set age方法');
      if(val<0){
            throw new Error('人的年龄不能小于零');
        }
        this._age = val;
    }
    // 获取数据
    get age():number{
      console.log('进入了get age方法');
      return this._age;
    }
  }
  let p = new Person();
  p.age = 34;
  // p.age = -6; // p.age(-6);
  console.log(p.age);
  • 5.抽象类
    • 抽象类是专门用于定义哪些不希望被外界直接创建的类
    • 抽象类一般用于定义基类
    • 抽象类和接口一样用于约束子类
  • 抽象类和接口区别:
    • 接口中只能定义约束, 不能定义具体实现
    • 而抽象类中既可以定义约束, 又可以定义具体实现
/*
class Person {
    name:string;
    age: number;
    // 可以使用protected:约束基类不能被创建对象
    protected constructor(name:string, age:number){
        this.name = name;
        this.age = age;
    }
}
class Student extends Person{
    constructor(name:string, age:number){
        super(name, age);
    }
}
// let p = new Person('lnj', 34);
let stu = new Student('lnj', 34);
console.log(stu);
 */

// abstract 抽象类
  abstract class Person{
    // 约束子类必须有name属性与say方法
    abstract name:string;
    abstract say():void;
    // 实现一个自己的方法
    eat():void{
      console.log(`${this.name}正在吃东西`);
    }
  }
  // 继承
  class Student extends Person{
    name:string = 'lnj';
    say():void{
        console.log(`我的名字是${this.name}`);
    }
  }
  let stu = new Student();
  stu.say();
  stu.eat();
  • 6.类 实现 接口(implements
// 1.定义接口
interface PersonInterface {
    name:string;
    say():void;
}
// 2.定义类,来实现接口
// 只要实现的某一个接口, 那么就必须实现接口中所有的属性和方法
class Person implements PersonInterface{
    name:string = 'lnj';
    say():void{
        console.log(`我的名字叫:${this.name}`);
    }
}
let p = new Person();
p.say();
  • 7.接口 继承 类(extends
// 1.定义类
class Person {
    // protected name:string = 'lnj';
    name:string = 'lnj';
    age:number = 34;
    protected say():void{
        console.log(`name = ${this.name}, age = ${this.age}`);
    }
}
// let p = new Person();
// p.say();
// 注意点: 只要一个接口继承了某个类, 那么就会继承这个类中所有的属性和方法
//         但是只会继承属性和方法的声明, 不会继承属性和方法实现
// 注意点: 如果接口继承的类中包含了protected的属性和方法, 那么就只有这个类的子类才能实现这个接口
// 2.定义接口,继承自Person类
interface PersonInterface extends Person{
    gender:string;
}

class Student extends Person implements PersonInterface{
// class Student implements PersonInterface{
    gender:string = 'male';
    name:string = 'zs';
    age:number = 18;
    say():void{
        console.log(`name = ${this.name}, age = ${this.age}, gender = ${this.gender}`);
    }
}
let stu = new Student();
stu.say();
  • 8.泛型类
// 泛型类
class Chache<T> {
    arr:T[] = [];
    add(value:T):T{
        this.arr.push(value);
        return value;
    }
    all():T[]{
        return this.arr;
    }
}
let chache = new Chache<number>();
chache.add(1);
chache.add(3);
chache.add(5);
console.log(chache.all());
  • 9.接口合并现象
    • 当我们定义了多个同名的接口时, 多个接口的内容会自动合并
// 1.定义接口
interface TestInterface {
    name:string;
}
interface TestInterface {
    age:number;
}
/*
interface TestInterface {
    name:string;
    age:number;
}
* */
// 2.利用函数,实现接口
class Person implements TestInterface{
    age:number = 19;
    name:string = 'lnj';
}

7.枚举

  • 1.数字枚举
  // 默认情况下就是数字枚举
  enum Gender{
    Male,
    Female
  }
  console.log(Gender.Male);    // 0
  console.log(Gender.Female);  // 1
  • 注意点
    • 数字枚举的取值,默认从0开始递增
    • 数字枚举的取值:可以是字面量, 也可以是常量, 也可以是计算的结果
  const num = 666;
  function getNum() {
      return 888;
  }
  enum Gender{
      // 1.常量
      // Male = 6,
      // 2.字面量
      // Male = num, // 注意点: 如果使用常量给前面的枚举值赋值了, 那么后面的枚举值也需要手动的赋值
      // Female = 8
      // 3.计算结果
      Male = getNum(), // 注意点: 如果使用计算结果给前面的枚举值赋值了, 那么后面的枚举值也需要手动的赋值
      Female = 123
  }
  • 2.枚举反向映射
// 可以根据枚举值获取到原始值
// 也可以根据原始值获取到枚举值
enum Gender{
    Male,
    Female
}
console.log(Gender.Male);   // 0
console.log(Gender[0]);    // Male
  • 3.字符串枚举
enum Gender{
    Male = 'www.it666.com',
    Female = 'www.itzb.com'   // 注意点: 如果使用字符串给前面的枚举值赋值了, 那么后面的枚举值也必须手动赋值
} 
console.log(Gender.Male);     // www.it666.com
console.log(Gender.Female);   // www.itzb.com
  •  注意点
    • 1.如果使用字符串给前面的枚举值赋值了, 那么后面的枚举值也必须手动赋值

    • 2.和数字枚举不一样, 字符串枚举不能使用常量或者计算结果给枚举值赋值

    • 3.虽然字符串枚举不能够使用常量或者计算结果给枚举值赋值, 但是它可以使用内部的其它枚举值来赋值

const str = 'lnj';
function getStr() {
    return 'abc';
}
enum Gender{
    Male = 'www.it666.com',
    // Male = str,
    // Male = getStr(),
    Female = 'www.itzb.com',
    Yao = Female
}
console.log(Gender.Female);
console.log(Gender.Yao);
  • 4.枚举异构
    • 枚举中既包含数字又包含字符串, 我们就称之为异构枚举
enum Gender{
    Male = 6,
    Female = 'nv'
}
console.log(Gender.Male);
console.log(Gender.Female);
console.log(Gender[6]);
// 注意点: 如果是字符串枚举, 那么无法通过原始值获取到枚举值
// console.log(Gender['nv']);
console.log(Gender);
  • 5.枚举成员类型
enum Gender{
    // 数字枚举
    // Male,
    // Female
    // 字符串枚举
    Male = 'www.it666.com',
    Female = 'www.itzb.com'
}
interface TestInterface {
    age: Gender.Male
}
class Person implements TestInterface{
    age: Gender.Male
    // age: Gender.Female   // 由于类型不匹配, 所以会报错
    // age: 0               // 注意点: 由于数字枚举的本质就是数值, 所以这里写一个数值也不会报错

    // age: Gender.Male
    // age: Gender.Female
    // age: 'www.it666.com' // 注意点: 如果是字符串枚举, 那么只能是枚举成员的值, 不能是其它的值
    // age: string
}
  • 6.联合枚举类型
    • 联合类型,就是将多种数据类型,通过 | 连接起来
    • 我们可以把枚举类型,当做一个联合类型来使用
let value:(number | string); // (number | string)联合类型
value = 1;
value = 6;
value = "123";
enum Gender{
    Male,
    Female
}
// 定义接口
interface TestInterface {
    age: Gender // age: (Gender.Male | Gender.Female)
}
// 实现接口
class Person implements TestInterface{
    // age: Gender.Male
    age: Gender.Female
}
  • 7.运行时枚举
    • 枚举:在编译之后是一个真实存储的对象, 所以可以在运行时使用
    • 接口:只是用来做约束做静态检查的代码, 编译之后是不存在的
interface TestInterface {
    name:string;
    age:number;
}
enum Gender{
    Male,
    Female
}
  • 8.常量枚举
    • 普通枚举和常量枚举的区别
    • 普通枚举:会生成真实存在的对象( const
    • 常量枚举:不会生成真实存在的对象, 而是利用枚举成员的值直接替换使用到的地方
enum Gender1{
    Male,
    Female
}
console.log(Gender1.Male === 0);

// 常量枚举
const enum Gender2{
    Male,
    Female
}
console.log(Gender2.Male === 0);

8.类型推论和兼容

  • 1.自动类型推断
    • 不用明确告诉编译器具体是什么类型, 编译器就知道是什么类型
  • 1.1 根据初始化值,自动推断
// 如果是先定义再初始化, 那么是无法自动推断的
// let value;
// value = 123;
// value = false;
// value = 'abc';

// 如果是定义的同时初始化, 那么TS就会自动进行类型推荐
// let value = 123; // let value:number = 123;
// value = 456;
// value = false;
// value = 'abc';

let arr = [1, 'a']; // let arr:(number | string) = [1, 'a'];
arr = ['a', 'b', 'c', 1, 3, 5, false]
  • 1.2 根据上下文类型自动推断
window.onmousedown = (event)=>{
    console.log(event.target);
}
  • 2.类型兼容性
interface TestInterface {
    name:string;
}

let p1 = {name:'lnj'};
let p2 = {age:18};
let p3 = {name:'lnj', age:18};

let t:TestInterface;
t = p1;
t = p2;
t = p3; // 可多不可少
interface TestInterface {
    name:string;
    children:{
        age:number
    };
}

let p1 = {name:'lnj', children:{age:18}};
let p2 = {name:'lnj',children:{age:'abc'}};

let t:TestInterface;
t = p1;
t = p2; // 会递归检查
  • 3.函数兼容性
  • 3.1 参数个数
let fn1 = (x:number, y:number)=>{};
let fn2 = (x:number)=>{};
fn1 = fn2;
fn2 = fn1; // 可少不可多
  • 3.2 参数类型
let fn1 = (x:number)=>{};
let fn2 = (x:number)=>{};
let fn3 = (x:string)=>{};
fn1 = fn2;
fn2 = fn1;
fn1 = fn3; // 必须一模一样
fn3 = fn1;
  • 3.3 返回值类型
let fn1 = ():number=> 123;
let fn2 = ():number=> 456;
let fn3 = ():string=> 'abc';
fn1 = fn2;
fn2 = fn1;
fn1 = fn3; // 必须一模一样
fn3 = fn1;
  • 3.4 函数双向协变
// 参数的双向协变
// let fn1 = (x:(number | string)) =>{};
// let fn2 = (x:number) =>{};
// fn1 = fn2;
// fn2 = fn1;

// 返回值双向协变
let fn1 = (x:boolean):(number | string) => x ? 123 : 'abc';
let fn2 = (x:boolean):number => 456;
// fn1 = fn2; // 但是可以将返回值是具体类型的赋值给联合类型的
fn2 = fn1; // 不能将返回值是联合类型的赋值给具体类型的
  • 3.5 函数重载
function add(x:number, y:number):number;
function add(x:string, y:string):string;
function add(x, y) {
    return x + y;
}

function sub(x:number, y:number):number;
function sub(x, y) {
    return x - y;
}

// let fn = add;
// fn = sub; // 不能将重载少的赋值给重载多的

let fn = sub;
fn = add; // 可以将重载多的赋值给重载少
  • 4.枚举兼容性
  • 4.1 数字枚举与数值兼容
enum Gender{
    Male,
    Female
}
let value:Gender;
value = Gender.Male;
value = 1
  • 4.2 数字枚举与数字枚举不兼容
enum Gender{
    Male, // 0
    Female // 1
}
enum Animal{
    Dog, // 0
    Cat // 1
}
let value:Gender;
value = Gender.Male;
value = Animal.Dog; // 报错
  • 4.3 字符串枚举与字符串不兼容
enum Gender{
    Male = 'abc',
    Female  = 'def'
}
let value:Gender;
value = Gender.Male;
value = 'abc';
  • 5.类兼容性
  • 5.1 只比较实例成员, 不比较类的构造函数和静态成员
class Person {
    public name:string;
    // public age:number;
    public static age:number;
    constructor(name:string, age:number){}
}
class Animal {
    public name:string;
    constructor(name:string){}
}
let p: Person;
let a: Animal;
p = a;
a = p; // 可多不少
  • 5.2 类的私有属性和受保护属性,会影响兼容性
class Person {
    protected name:string;
}
class Animal {
    protected name:string;
}
let p: Person;
let a: Animal;
p = a;   // 报错
a = p;   // 报错
  • 6.泛型兼容性
  • 泛型只影响使用的部分, 不会影响声明的部分
interface TestInterface<T> {
    // age:T;
}
let t1: TestInterface<number>; // age:number
let t2: TestInterface<string>; // age:string
t1 = t2;
t2 = t1;

9.高级类型

10.模块与命名空间

  • 1.模块
  • ES6 模块
1.1 分开导入导出
export xxx;
import {xxx} from "path";

1.2 一次性导入导出
export {xxx, yyy, zzz};
import {xxx, yyy, zzz} from "path";

1.3 默认导入导出
export default xxx;
import xxx from "path";
  • Node 模块
1.1 通过 exports.xxx = xxx 导出
通过 const xxx = require("path"); 导入
通过 const {xx, xx} = require("path"); 导入

1.2 通过module.exports.xxx = xxx 导出
通过 const xxx = require("path"); 导入
通过 const {xx, xx} = require("path"); 导入
  • TS 模块
ES6的模块和Node的模块是不兼容的, 所以TS为了兼容两者就推出了

export = xxx;
import xxx = require('path');
  • 2.命名空间
  • 2.1 什么是命名空间
    • 命名空间可以看做是一个微型模块,
    • 当我们先把相关的业务代码写在一起, 又不想污染全局空间的时候, 我们就可以使用        命名空间
    • 本质就是定义一个大对象, 把变量/方法/类/接口...的都放里面
  • 2.2 命名空间和模块区别
    • 在程序内部使用的代码, 可以使用命名空间封装和防止全局污染
    • 在程序内部外部使用的代码, 可以使用模块封装和防止全局污染
    • 总结: 由于模块也能实现相同的功能, 所以大部分情况下用模块即可

test.ts

// 定义命名空间
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export const LettersValidator  = (value) =>{
        return lettersRegexp.test(value);
    }
}

use.ts

// 导入命名空间
/// <reference path="./test.ts" />
console.log(Validation.LettersValidator('abc'));
console.log(Validation.LettersValidator(123));
  • 3.接口合并与命名空间的合并
  • 在ts当中接口和命名空间是可以重名的, ts会将多个同名的合并为一个
  • 1.接口
interface TestInterface {
    name:string;
}
interface TestInterface {
    age:number;
}
// interface TestInterface {
//     name:string;
//     age:number;
// }
class Person implements TestInterface{
    name:string;
    age:number;
}
  • 1.1 同名接口如果属性名相同, 那么属性类型必须一致
interface TestInterface {
    name:string;
}
interface TestInterface {
    name:number;
}
  • 1.2 同名接口如果出现同名函数, 那么就会成为一个函数的重载
interface TestInterface {
    getValue(value:number):number;
}
interface TestInterface {
    getValue(value:string):number;
}
let obj:TestInterface = {
    getValue(value:any):number{
        if(typeof value === 'string'){
            return value.length;
        }else{
            return value.toFixed();
        }
    }
}
console.log(obj.getValue("abcdef"));
console.log(obj.getValue(3.14));
  • 2.命名空间
namespace Validation{
    export let name:string = 'lnj';
}
namespace Validation{
    export let age:number = 18;
}
console.log(Validation.name);
console.log(Validation.age);
  • 2.1 同名的命名空间中,不能出现同名的变量,方法等
namespace Validation{
    export let name:string = 'lnj';
    export let say = ()=> "abc";
}
namespace Validation{
    export let name:string = 'zs';
    export let say = ()=> "abc";
}
  • 2.2 同名的命名空间中,其它命名空间,没有通过 export 导出的内容是获取不到的
namespace Validation{
    let name:string = 'lnj';
}
namespace Validation{
    export let say = ()=> {
        console.log(`name = ${name}`);
    };
}
Validation.say();
  • 除了同名的接口和命名空间可以合并以外
  • 命名空间,还可以和同名的 类 / 函数 / 枚举 合并
  • 1.命名空间和类合并
// 注意点: 类必须定义在命名空间的前面
// 会将命名空间中导出的方法作为一个静态方法合并到类中
class Person {
    say():void{
        console.log('hello world');
    }
}
namespace Person{
    export const hi = ():void=>{
        console.log('hi');
    }
}
console.dir(Person);
  • 2.命名空间和函数合并
// 注意点: 函数必须定义在命名空间的前面
function getCounter() {
    getCounter.count++;
    console.log(getCounter.count);
}
namespace getCounter{
    export let count:number = 0;
}
  • 3.命名空间和枚举合并
// 注意点: 没有先后顺序的要求
enum Gender {
    Male,
    Female
}
namespace Gender{
    export const Yao:number = 666;
}
console.log(Gender);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值