typescript

一、TypeScript 是什么

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件。下图显示了 TypeScript 与 ES5、ES2015 和 ES2016 之间的关系:

图片

1.1 为什么需要TypeScript?

简单来说就是因为JavaScript是弱类型, 很多错误只有在运行时才会被发现
而TypeScript提供了一套静态检测机制, 可以帮助我们在编译时就发现错误

1.2 TypeScript 与 JavaScript 的区别

TypeScriptJavaScript
JavaScript 的超集用于解决大型项目的代码复杂性一种脚本语言,用于创建动态网页。
可以在编译期间发现并纠正错误作为一种解释型语言,只能在运行时发现错误
强类型,支持静态和动态类型弱类型,没有静态类型选项
最终被编译成 JavaScript 代码,使浏览器可以理解可以直接在浏览器中使用
支持模块、泛型和接口不支持模块,泛型或接口
社区的支持仍在增长,而且还不是很大大量的社区支持以及大量文档和解决问题的支持

1.3 获取 TypeScript

命令行的 TypeScript 编译器可以使用 Node.js 包来安装。

1.安装 TypeScript

npm install -g typescript  // 全局安装 ts

不记得自己是否已经安装过 typescript 的,可以使用以下命令来验证:

tsc -v 

如果出现版本,则说明已经安装成功

Version 4.9.4

2.编译 TypeScript 文件

生成 tsconfig.json 配置文件

tsc --init

执行命令后我们就可以看到生成了一个 tsconfig.json 文件,里面有一些配置信息
在我们helloworld.ts文件中,随便写点什么

const s:string = "彼时彼刻,恰如此时此刻";
console.log(s);

控制台执行 tsc helloworld.ts 命令,目录下生成了一个同名的 helloworld.js 文件,代码如下

var s = "彼时彼刻,恰如此时此刻";
console.log(s);

通过tsc命令,发现我们的typescript代码被转换成了熟悉的js代码

我们接着执行

node helloworld.js

即可看到输出结果

当然,对于刚入门 TypeScript 的小伙伴,也可以不用安装 typescript,而是直接使用线上的 TypeScript Playground 来学习新的语法或新特性。

TypeScript Playground:https://www.typescriptlang.org/play/

备注:

  1. 定义任何东西的时候要注明类型
  2. 调用任何东西的时候要检查类型

1.4 安装ts-node

那么通过我们上面的一通操作,我们知道了运行tsc命令就可以编译生成一个js文件,但是如果每次改动我们都要手动去执行编译,然后再通过 node命令才能查看运行结果岂不是太麻烦了。

而 ts-node 正是来解决这个问题的

npm i -g ts-node // 全局安装ts-node

有了这个插件,我们就可以直接运行.ts文件了

ts-node index.ts 

1.5 ts类型等级

第一级类型可以包含下面所有的类型,以此类推

第一级: any、unknown
第二级: Object
第三级: Number、String 、Boolean
第四级: number、string 、boolean
第五级: 1、'dd'、false (字面量类型)
第六级: never

1.6 object、Object 以及{} 的区别

1.6.1 Object

Object类型是所有Object类的实例的类型。 由以下两个接口来定义:

  • Object 接口定义了 Object.prototype 原型对象上的属性;
  • ObjectConstructor 接口定义了Object 类的属性, 如上面提到的 Object.create()。

这个类型是跟原型链有关的原型链顶层就是Object,所以值类型和引用类型最终都指向Object,所以他包含所有类型。几乎不用,范围太大了

let obj:Object
obj = {}
obj = []
obj = function(){}
obj = 1  // 1不是Object的实例对象,但其包装对象是Object的实例
obj = true // true不是Object的实例对象,但其包装对象是Object的实例
obj = 'a' // 'a'不是Object的实例对象,但其包装对象是Object的实例

obj = null // 不能将类型“null”分配给类型“Object”
obj = undefined // 不能将类型“undefined”分配给类型“Object”

1.6.2 object

object 代表所有非【基本类型】,例如 数组、对象、 函数等,常用于泛型约束

let o:object = {} //正确
let o1:object = [] //正确
let o2:object = ()=>123 //正确

let b:object = '123' //错误
let c:object = 123 //错误

1.6.3 {}

看起来很别扭的一个东西 你可以把他理解成new Object 就和我们的第一个Object基本一样 包含所有类型

tips 字面量模式是不能修改值的

let a1: {} = {name:1} //正确
let a2: {} =  () => 123//正确
let a3: {} = 123//正确

二、基本类型

1.1 类型

类型例子描述
number1, -33, 2.5任意数字
string‘hi’, “hi”, hi任意字符串
booleantrue、false布尔值true或false
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
object{name:‘孙悟空’}任意的JS对象
array[1,2,3]任意JS数组
tuple[4,5]元素,TS新增类型,固定长度数组
enumenum{A, B}枚举,TS中新增类型

2.1 Number 类型

支持十六进制、十进制、八进制和二进制;

let count: number = 10;
// ES5:var count = 10;

let notANumber: number = NaN;//NaN
let num: number = 123;//普通数字
let infinityNumber: number = Infinity;//无穷大
let decimal: number = 6;//十进制
let hex: number = 0xf00d;//十六进制
let binary: number = 0b1010;//二进制
let octal: number = 0o744;//八进制s

2.2 String 类型

let str: string = "hi";
// ES5:var str = 'hi';
  
//也可以使用es6的字符串模板
let str1: string = `dddd${str}` // ddddhi

2.3 Boolean 类型

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

let createdBoolean: boolean = new Boolean(1)
//这样会报错 应为事实上 new Boolean() 返回的是一个 Boolean 对象 

let booleand: boolean = true //可以直接使用布尔值
// Boolean(1) // true
let booleand2: boolean = Boolean(1) //也可以通过函数返回布尔值   

2.4 字面量 类型

也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围

let color: 'red' | 'blue' | 'black';
let num: 1 | 2 | 3 | 4 | 5;

2.5 any 类型

在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型.

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

let a: string = 'seven';
a = 7;
// TS2322: Type 'number' is not assignable to type 'string'.

但如果是 any 类型,则允许被赋值为任意类型。

let a: any = 666;
a = "Semlinker";
a = false;
a = 66
a = undefined
a = null
a = []
a = {}

在any上访问任何属性都是允许的,也允许调用任何方法.

let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

let something;
something = 'seven';
something = 7;

在许多场景下,这太宽松了。使用 any 类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any 类型,就无法使用 TypeScript 提供的大量的保护机制,就失去了TS类型检测的作用。请记住,any 是魔鬼!尽量不要用any。

为了解决 any 带来的问题,TypeScript 3.0 引入了 unknown 类型。

2.6 unknown 类型

unknown 类型也被认为是 top type,但它更安全。与 any 一样,所有类型都可以分配给unknown

unknow unknow类型比any更加严格当你要使用any 的时候可以尝试使用unknow
unknownany一样,所有类型都可以分配给unknown:

let notSure: unknown = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK

2.6.1 unknown 和 any的区别

unknownany的最大区别是: 任何类型的值都可以赋值给any,同时any类型的值也可以赋值给任何类型。

unknown 任何类型的值都可以赋值给它,但它只能赋值给unknownany

unknownany更加安全,优先使用unkonwn

let notSure1: unknown = 4;
let uncertain1: any = notSure1; // OK

let notSure2: any = 4;
let uncertain2: unknown = notSure2; // OK

let notSure3: unknown = 4;
let uncertain3: number = notSure3; // Error

let notSure4: any = 4;
let uncertain4: number = notSure4; // OK

let e: unknown;
let s:string;
// unknown 实际上就是一个类型安全的any
// unknown类型的变量,不能直接赋值给其他变量
if(typeof e === "string"){
    s = e;
}
// 如果是any类型在对象没有这个属性的时候还在获取是不会报错的
let obj:any = {b:1}
obj.a
 
 
// 如果是unknow 是不能调用属性和方法
let obj:unknown = {b:1,ccc:():number=>213}
obj.b
obj.ccc()

2.7 void 类型

void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void

声明一个 void 类型的变量没有什么作用,因为它的值只能为 undefined
空 ,严格模式下不能将null赋值给void,不是严格模式下可以,一般也只有在函数没有返回值时去声明

let test: void = undefined
let test1: void = null  // null 不能赋予 void类型(严格模式下)

// 返回undefined就相当于没返回值
function fn3():void{
    return undefined
}

function fn2():void{
    return
}

// void 用来表示空值,以函数为例,就表示没有返回值的函数
function fn(): void{
}

2.8 Never 类型

never 类型表示的是那些永不存在的值的类型。来表示不应该存在的状态
例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

// 返回never的函数必须存在无法达到的终点
// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
  throw new Error(message);
}

// 因为存在死循环,所以 loop 将不会有返回值
function infiniteLoop(): never {
  while (true) {}
}

never 与 void 的差异

//void类型只是没有返回值 但本身不会出错
 function Void():void {
     console.log();
 }

 //只会抛出异常没有返回值
 function Never():never {
 throw new Error('aaa')
 }

//差异2   当我们鼠标移上去的时候会发现 只有void和number    
// never在联合类型中会被直接移除
type A = void | number | never

never 类型的一个应用场景

type A = '小满' | '大满' | '超大满' 
function isXiaoMan(value:A) {
   switch (value) {
       case "小满":
           break 
       case "大满":
          break 
       case "超大满":
          break 
       default:
          //是用于场景兜底逻辑
          const error:never = value;
          return error
   }
}

//由于任何类型都不能赋值给 never 类型的变量,所以当存在进入 default 分支的可能性时,TS的类型检查会及时帮我们发现这个问题
type A = '小满' | '大满' | '超大满' | "小小满"
function isXiaoMan(value:A) {
   switch (value) {
       case "小满":
           break 
       case "大满":
          break 
       case "超大满":
          break 
       default:
          //是用于场景兜底逻辑
          const error:never = value;
          return error
   }
}

思考:never 和 void 的区别 void 可以被赋值为 null(在strictNullChecks未指定为true时) 和 undefined 的类型。 never 则是一个不包含值的类型。 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。

2.9 Null 和 Undefined 类型

TypeScript 里,undefinednull 两者有各自的类型分别为 undefinednull

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

默认情况下 nullundefined 是所有类型的子类型。 就是说你可以把 nullundefined 赋值给 number 类型的变量。严格模式下,undefined 只能赋值给 void 和它自己的类型。
null 只能赋值给它自己的类型

2.10 Array 类型

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

let list1: Array<number> = [1, 2, 3]; // Array<number>泛型语法
// ES5:var list = [1,2,3];

//类型加中括号
let arr1:number[] = [123]
//这样会报错定义了数字类型出现字符串是不允许的
let arr2:number[] = [1,2,3,'1']
//操作方法添加也是不允许的
let arr3:number[] = [1,2,3,]
arr3.unshift('1')
 
var arr4: number[] = [1, 2, 3]; //数字类型的数组
var arr5: string[] = ["1", "2"]; //字符串类型的数组
var arr6: any[] = [1, "2", true]; //任意类型的数组

用接口表示数组(一般用来描述类数组)

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//表示:只要索引的类型是数字时,那么值的类型必须是数字。

使用interface定义对象数组

interface X {
  name: string;
  age?: number;
}

let arr: X[] = [{ name: "xx" }, { name: "aa" }];

多维数组

let data:number[][] = [[1,2], [3,4]];
let data1: Array<Array<number>> =  [[1,2], [3,4]];

any 在数组中的应用
一个常见的例子数组中可以存在任意类型

let list: any[] = ['test', 1, [],{a:1}]
let list1: [string,number,Array<number>,object] = ['test', 1, [],{a:1}]

arguments类数组

function Arr(...args: any[]): void {
  console.log(arguments);
  //错误的arguments 是类数组不能这样定义
  let arr: number[] = arguments;
}
Arr(111, 222, 333);

function Arr1(...args: any[]): void {
  console.log(arguments);
  //ts内置对象IArguments 定义
  let arr: IArguments = arguments;
}
Arr1(111, 222, 333);
 
//其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}

2.11 元组 Tuple 类型

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
元组最重要的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回。
元组(Tuple)是固定数量的不同类型的元素的组合。

let x: [string, number]; 
// 类型必须匹配且个数必须为2

x = ['hello', 10]; // OK 
x = ['hello', 10,10]; // Error 
x = [10, 'hello']; // Error
// 越界元素
x.push(true)//error。

2.12 object 类型

  • object
    object 类型用于表示所有的非原始类型,即我们不能把 number、string、boolean、symbol等 原始类型赋值给 object。在strictNullChecks:true下,null 和 undefined 类型也不能赋给 object。
let o:object = {}//正确
let o1:object = []//正确
let o2:object = ()=>123 //正确
let b:object = '123' //错误
let c:object = 123 //错误

let object: object;
object = 1; // 报错
object = "a"; // 报错
object = true; // 报错
object = null; // 报错
object = undefined; // 报错
object = {}; // 编译正确
  • Object

大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)

Object类型是所有Object类的实例的类型。 由以下两个接口来定义:
Object 接口定义了 Object.prototype 原型对象上的属性;
ObjectConstructor 接口定义了 Object 类的属性, 如上面提到的 Object.create()。
这个类型是跟原型链有关的原型链顶层就是Object,所以值类型和引用类型最终都指向Object,所以他包含所有类型。

let object: Object;
object = 1; // 编译正确
object = "a"; // 编译正确
object = true; // 编译正确
object = null; // 报错
object = undefined; // 报错
object = {}; // ok
  • {}

{} 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合

let simpleCase: {};
simpleCase = 1; // ok
simpleCase = "a"; // ok
simpleCase = true; // ok
simpleCase = null; // error
simpleCase = undefined; // error
simpleCase = {}; // ok

let a1: {} = {name:1} //正确
a1.name = "zz" // 错误
let a2: {} =  () => 123//正确
let a3: {} = 123//正确

字面量模式是不能修改值的

2.13 Enum 类型

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举。

enum Gender{
    Male,
    Female
}

let i: {name: string, gender: Gender};
i = {
    name: '孙悟空',
    gender: Gender.Male 
}
console.log(i)

2.13.1数字枚举

红绿蓝 Red = 0 Green = 1 Blue= 2 分别代表红色0 绿色为1 蓝色为2

enum Types{
   Red,
   Green,
   BLue
}

// 这样写就可以实现应为ts定义的枚举中的每一个组员默认都是从0开始的所以也就是
enum Types{
   Red = 0,
   Green = 1,
   BLue = 2
}
//默认就是从0开始的 可以不写值

增长枚举
我们定义了一个数字枚举, Red使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Type.Red的值为 1, Green为 2, Blue为 3。

enum Types{
   Red = 1,
   Green,
   BLue
}

2.13.2 字符串枚举

字符串枚举的概念很简单。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。

enum Types{
   Red = 'red',
   Green = 'green',
   BLue = 'blue'
}

2.13.3 异构枚举

枚举可以混合字符串和数字成员

enum Types{
   No = "No",
   Yes = 1,
}

2.13.4 接口枚举

定义一个枚举Types 定义一个接口A 他有一个属性red 值为Types.yyds
声明对象的时候要遵循这个规则

   enum Types {
      yyds,
      dddd
   }
   interface A {
      red:Types.yyds
   }
 
   let obj:A = {
      red:Types.yyds
   }

2.13.5 const枚举

let 和 var 都是不允许的声明只能使用const
大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义

const 声明的枚举会被编译成常量
普通声明的枚举编译完后是个对象

const enum Types{
   No = "No",
   Yes = 1,
}

2.13.1 反向映射

它包含了正向映射( name -> value)和反向映射( value -> name)
要注意的是 不会为字符串枚举成员生成反向映射。

enum Enum {
   fall
}
let a = Enum.fall;
console.log(a); //0
let nameOfA = Enum[a]; 
console.log(nameOfA); //fall

2.14 symbol类型

可以传递参做为唯一标识 只支持 string 和 number类型或空的参数

let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key
let sym3 = Symbol(1);  

// Symbol的值是唯一的
const s1 = Symbol()
const s2 = Symbol()
// s1 === s2 =>false

如何让2个symbol的值相等

// for Symbol for全局symbol有没没有注册过这个key 如果有直接拿来用没有的话他就去创建一个 
console.log(Symbol.for('zx') === Symbol.for('zx'))

使用symbol定义的属性,是不能通过如下方式遍历拿到的
for…in, Object.keys, getOwnPropertyNames, JSON.stringfy

const symbol1 = Symbol('666')
const symbol2 = Symbol('777')
const obj1= {
   [symbol1]: '小满',
   [symbol2]: '二蛋',
   age: 19,
   sex: '女'
}
// 1 for in 遍历 不能读symbol
for (const key in obj1) {
   // 注意在console看key,是不是没有遍历到symbol1
   console.log(key)  // age sex
}
// 2 Object.keys 遍历 不能读symbol
console.log(Object.keys(obj1)) // [ 'age', 'sex' ]
// 3 getOwnPropertyNames 不能读symbol
console.log(Object.getOwnPropertyNames(obj1)) //  [ 'age', 'sex' ]
// 4 JSON.stringfy 不能读symbol
console.log(JSON.stringify(obj1))  // {"age":19,"sex":"女"}

如何拿到

// 1 拿到具体的symbol 属性,对象中有几个就会拿到几个,只能读symbol
console.log(Object.getOwnPropertySymbols(obj1)) // [ Symbol(666), Symbol(777) ]
// 2 es6 的 Reflect 拿到对象的所有属性
console.log(Reflect.ownKeys(obj1)) // [ 'age', 'sex', Symbol(666), Symbol(777) ]

作用: 主要是为了解决对象属性的key会重复的问题,在ES5中,‌对象的属性名都是字符串,‌这很容易导致属性名的冲突。‌例如,‌当你使用了一个由他人提供的对象,‌并试图为该对象添加新的方法时,‌新方法的名字就有可能与现有方法产生冲突。‌为了避免这种情况,‌ES6引入了一种新的原始数据类型Symbol,‌它表示独一无二的值。‌Symbol实例是唯一且不可变的,‌用于作为对象的属性键,‌可以确保每个属性的名字都是独一无二的,‌从而从根本上防止属性名的冲突

2.14.1 生成器

function * gen(){
    yield Promise.resolve('1')
    yield '2'
    yield '3'
}

const g = gen()
console.log(g.next()) // { value: Promise { '1' }, done: false }
console.log(g.next()) // { value: '2', done: false }
console.log(g.next()) // { value: '3', done: false }
console.log(g.next()) // { value: undefined, done: true }

2.14.2 迭代器

2.14.2.1 set
let set: Set<number>= new Set([1,1, 2, 2,3,3]) //天然去重 1 2 3
console.log(set);  // Set(3) { 1, 2, 3 }
2.14.2.2 map
 let map:Map<any, any> = new Map()
 let Arr = [1,2,3]
 map.set(Arr,"小满")
 console.log(map.get(Arr))  // 小满
2.14.2.3 伪数组
 function args (){
    console.log(arguments)//伪数组
 } 
 // let list = document.querySelectorAll('div')//伪数组
2.14.2.4 迭代器

迭代器能遍历上面的所有类型

// let it:Iterator<Item> = array[Symbol.iterator]()
const gen = (value:any): void => {
    let it: Iterator<any> = value[Symbol.iterator]()
    let next:any= { done: false }
    while (!next.done) {
        next =  it.next()
        if (!next.done) {
            console.log(next.value)
        }
    }
}
gen(Arr)
2.14.2.5 for…of

我们平时开发中不会手动调用iterator 因为 他是有语法糖的就是for…of
记住 for…of 是不能循环对象的应为对象没有iterator

for (let value of map) {
   console.log(value)  // [ [ 1, 2, 3 ], '小满' ]
}
2.14.2.6 数组解构

数组解构的原理其实也是调用迭代器的

var [a,b,c] = [1,2,3]
var x = [...Arr]
2.14.2.6 封装一个迭代器让对象支持for of

那我们可以自己实现一个迭代器让对象支持for of

const 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 {
                    return {
                        value: this.current++,
                        done: false
                    }
                }
            }
        }
    }
}
console.log([...obj])  // [ 0, 1, 2, 3, 4 ]
console.log({...obj})  
/* {
    max: 5,
    current: 0,
    [Symbol(Symbol.iterator)]: [Function: [Symbol.iterator]]
  } */
for (let val of obj) {
   console.log(val); // 0 1 2 3 4 
}

三、类型推论

我们把 TypeScript 这种基于赋值表达式推断类型的能力称之为类型推断

在 TypeScript 中,具有初始化值的变量、有默认值的函数参数、函数返回的类型都可以根据上下文推断出来。比如我们能根据 return 语句推断函数返回的类型,如下代码所示:

{
  /** 根据参数的类型,推断出返回值的类型也是 number */
  function add1(a: number, b: number) {
    return a + b;
  }
  const x1= add1(1, 1); // 推断出 x1 的类型也是 number
  
  /** 推断参数 b 的类型是数字或者 undefined,返回值的类型也是数字 */
  function add2(a: number, b = 1) {
    return a + b;
  }
}

指编程语言中能够自动推导出值的类型的能力 它是一些强静态类型语言中出现的特性 定义时未赋值就会推论成 any 类型 如果定义的时候就赋值就能利用到类型推论

let flag; //推断为any
let count = 123; //为number类型
let hello = "hello"; //为string类型

四、 联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种 未赋值时联合类型上只能访问两个类型共有的属性和方法

let name1: string | number;
console.log(name1.toString());
name1 = 1;
console.log(name1.toFixed(2));
name1 = "hello";
console.log(name1.length);

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

//例如我们的手机号通常是13XXXXXXX 为数字类型 这时候产品说需要支持座机
//所以我们就可以使用联合类型支持座机字符串
let myPhone: number | string  = '010-820'

五、 类型断言

有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。其实就是你需要手动告诉 ts 就按照你断言的那个类型通过编译(这一招很关键 有时候可以帮助你解决很多编译报错)

类型断言有两种形式:

// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as 语法
let someValue1: any = "this is a string";
let strLength1: number = (someValue as string).length;

以上两种方式虽然没有任何区别,但是尖括号格式会与 react 中 JSX 产生语法冲突,因此我们更推荐使用 as 语法。

非空断言 在上下文中当类型检查器无法断定类型时 一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型

let flag: null | undefined | string;
// 非 null 和非 undefined 类型
flag!.toString(); // ok
flag.toString(); // error

字面值的断言

const names = '小张'
names = 'aa' //无法修改
 
let names2 = '小张' as const
names2 = 'aa' //无法修改

// 数组
let a1 = [10, 20] as const;
const a2 = [10, 20];
 
a1.unshift(30); // 错误,此时已经断言字面量为[10, 20],数据无法做任何修改
a2.unshift(30); // 通过,没有修改指针

使用any临时断言

window.abc = 123
//这样写会报错因为window没有abc这个东西

(window as any).abc = 123
//可以使用any临时断言在 any 类型的变量上,访问任何属性都是允许的。

类型断言是不具影响力的

// 在下面的例子中,将 something 断言为 boolean 虽然可以通过编译,但是并没有什么用 并不会影响结果, 因为编译过程中会删除类型断言
function toBoolean(something: any): boolean {
    return something as boolean;
}
 
toBoolean(1);
// 返回值为 1

六、字面量类型

在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。 目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型,具体示例如下:

let flag1: "hello" = "hello";
let flag2: 1 = 1;
let flag3: true = true;

七、 类型别名

类型别名用来给一个类型起个新名字

// 定义类型别名
type str = string
let s:str = "我是小满"
// 定义函数别名
type str = () => string
let s: str = () => "我是小满"
// 定义值的别名
type value = boolean | 0 | '213' 
let s:value = true
//变量s的值  只能是上面value定义的值

// 定义联合类型别名
type flag = string | number;
function hello(value: flag) {}

hello(1)
hello('s')
hello(true)  // 报错

1. type 和 interface 还是一些区别的 虽然都可以定义类型

1.interface可以使用extends继承 type 只能通过 & 交叉类型合并
2.type 可以定义 联合类型 和 可以使用一些操作符 interface不行
3.interface 遇到重名的会合并, type 不行
4. 接口可以限制类的结构,可以当作type来使用

// type 只能通过 & 交叉类型合并
type s = number[] & string

interface b {}
// interface可以使用extends继承
interface a extends b{}
//type定义联合类型  
type s = number[] | string

interface b {}
interface a extends b{
// 属性可以定义,外面不行
name:string | number
}

// 接口当作自定义类型去使用
interface Person {
    speak(): void;
    name?: string; //?表示可选属性
}

// type Person =  {
//     speak(): void;
//     name?: string; //?表示可选属性
// }

// 接口当作自定义类型去使用
let person:Person  = {
    speak(){}
}

2. type高级用法

左边的值会作为右边值的子类型遵循图中上下的包含关系

//extends 包含的意思
//左边的值会作为右边类型的子类型 
// 1.any unknow
// 2.0bject
// 3.Number
// 4.number string
// 5.never
type a = 1 extends number ? 1 : 0 //1
type a = 1 extends Number ? 1 : 0 //1
type a = 1 extends Object ? 1 : 0 //1
type a = 1 extends any ? 1 : 0 //1
type a = 1 extends unknow ? 1 : 0 //1
type a = 1 extends never ? 1 : 0 //0

在这里插入图片描述

八、 交叉类型

交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

type Flag1 = { x: number };
type Flag2 = Flag1 & { y: string };

let flag3: Flag2 = {
  x: 1,
  y: "hello"
};

interface People {
  age: number,
   height: number
 }
 interface Man{
   sex: string
 }
 const xiaoman = (man: People & Man) => {
   console.log(man.age)
   console.log(man.height)
   console.log(man.sex)
 }
 xiaoman({age: 18,height: 180,sex: 'male'});

十、函数

1. 函数声明

// 普通函数
function add(x: number, y: number): number {
  return x + y;
}
// 箭头函数
const add1 = (x: number, y: number): number => x + y

function hello(name: string): void {
  console.log("hello", name);
}

2. 函数表达式

const add = function(x: number, y: number): number {
  return x + y;
}

3. 接口定义函数

//定义参数 num 和 num2  :后面定义返回值的类型
interface Add {
    (num:  number, num2: number): number
}
 
const fn: Add = (num: number, num2: number): number => {
    return num + num2
}
fn(5, 5)  // 10


interface User {
  name: string;
  age: number;
}
function getUserInfo(user: User): User {
  return user;
}

4. 可选参数

默认参数和可选参数不能一起使用,会报错

function add(x: number, y?: number): number {
  return y ? x + y : x;
}

5. 默认参数

默认参数和可选参数不能一起使用,会报错

function add(x: number, y: number = 0): number {
  return x + y;
}

6. 剩余参数

function sum(...numbers: number[]) {
  return numbers.reduce((val, item) => (val += item), 0);
}
console.log(sum(1, 2, 3)); // 6


const fn = (array:number[],...items:any[]):any[] => {
    console.log(array,items) // [ 1, 2, 3 ] [ '4', '5', '6' ]
    return items
}

let a:number[] = [1,2,3]

fn(a,'4','5','6')

7. 函数重载

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。

重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
如果参数类型不同,则参数类型应设置为 any。
参数数量不同你可以将不同的参数设置为可选

function add(x: number, y: number): number;
function add(x: string, y: string): string;

add(1,1)
add('a','b')

上面示例中,我们给同一个函数提供多个函数类型定义,从而实现函数的重载

需要注意的是:

函数重载真正执行的是同名函数最后定义的函数体 在最后一个函数体定义之前全都属于函数类型定义 不能写具体的函数实现方法 只能定义类型

8. 函数中定义this类型

interface Obj {
  user: number[];
  add: (this: Obj, num: number) => void;
}

// ts可以定义this的类型 在js中无法使用,必须是第一个参数定义this的类型
// js中this无法当作参数去使用
let obj: Obj = {
  user: [1, 2, 3],
  add(this: Obj, num: number) {
    this.user.push(num);
  },
};

obj.add(2);

十一、类

1. 类的定义

在 TypeScript 中,我们可以通过 Class 关键字来定义一个类。
在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明

class Person {
  // 在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明
  name: string;
  constructor(_name: string) {
    this.name = _name;
  }
  getName(): void {
    console.log(this.name);
  }
}
let p1 = new Person("hello");
p1.getName();

2. 存取器

在 TypeScript 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为

class User {
  myname: string;
  constructor(myname: string) {
    this.myname = myname;
  }
  get name() {
    return this.myname;
  }
  set name(value) {
    this.myname = value;
  }
}

let user = new User("hello");
user.name = "world";
console.log(user.name);

3. readonly 只读属性

只读属性必须在声明时或构造函数里被初始化。

class Animal {
  public readonly name: string;
  constructor(name: string) {
    this.name = name;
  }
  changeName(name: string) {
    this.name = name; //这个ts是报错的
  }
}

let a = new Animal("hello");

4. 继承

子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性

将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑

super 可以调用父类上的方法和属性

在 TypeScript 中,我们可以通过 extends 关键字来实现继承

class Person {
  name: string; //定义实例的属性,默认省略public修饰符
  age: number;
  constructor(name: string, age: number) {
    //构造函数
    this.name = name;
    this.age = age;
  }
  getName(): string {
    return this.name;
  }
  setName(name: string): void {
    this.name = name;
  }
}
class Student extends Person {
  no: number;
  constructor(name: string, age: number, no: number) {
    super(name, age);
    this.no = no;
  }
  getNo(): number {
    return this.no;
  }
}
let s1 = new Student("hello", 10, 1);
console.log(s1);

5. 类里面的修饰符

public 任何地方都可以访问,默认不写就是public
protected 类里面和继承的子类中访问 不能在外部访问
private 变量私有,类里面可以访问,子类和其它任何地方都不可以访问

class Parent {
  public name: string;
  protected age: number;
  private car: number;
  constructor(name: string, age: number, car: number) {
    //构造函数
    this.name = name;
    this.age = age;
    this.car = car;
  }
  getName(): string {
    return this.name;
  }
  setName(name: string): void {
    this.name = name;
  }
}
class Child extends Parent {
  constructor(name: string, age: number, car: number) {
    // 父类的prototype.constructor.call 可以给父类传参
    super(name, age, car);
    // 也可以直接通过super调用父类的属性和方法
    super.getName()
  }
  desc() {
    console.log(`${this.name} ${this.age} ${this.car}`);   //car访问不到 会报错
  }
}

let child = new Child("hello", 10, 1000);
console.log(child.name);
console.log(child.age); //age访问不到 会报错
console.log(child.car); //car访问不到 会报错

6. 静态属性 静态方法

类的静态属性和方法是直接定义在类本身上面的,不可以通过this 去访问,只能通过直接调用类的方法和属性来访问
需注意: 如果两个函数都是static 静态的是可以通过this互相调用

class Parent {
  static mainName = "Parent";
  static getmainName() {
  // 注意静态方法里面的this指向的是类本身 而不是类的实例对象 
  // 所以静态方法里面只能访问类的静态属性和方法
    console.log(this); 
    return this.mainName;
  }
  public name: string;
  constructor(name: string) {
    //构造函数
    this.name = name;
    // static 定义的属性和方法 不可以通过this 去访问 只能通过类名去调用
    this.name = this.mainName  // 报错
    this.getmainName()   // 报错
  }
}
console.log(Parent.mainName);
console.log(Parent.getmainName());

7. 抽象类和抽象方法

抽象类,无法被实例化(不能使用new来实例化),只能被继承并且无法创建抽象类的实例 子类可以对抽象类进行不同的实现

抽象方法只能出现在抽象类中并且抽象方法不能在抽象类中被具体实现,只能在抽象类的子类中实现(必须要实现)

使用场景: 我们一般用抽象类和抽象方法抽离出事物的共性 以后所有继承的子类必须按照规范去实现自己的具体逻辑 这样可以增加代码的可维护性和复用性

使用 abstract 关键字来定义抽象类和抽象方法
abstract 所定义的类就是抽象类
abstract 所定义的方法都只能描述不能进行一个实现

abstract class Animal {
// 定义一个抽象方法
// 抽象方法使用 abstract开头,没有方法体
// 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
  abstract speak(): void;
}
// 使用派生类来继承抽象类,必须实现抽象类中的方法,否则报错
class Cat extends Animal {
// 抽象方法子类中实现
  speak() {
    console.log("喵喵喵");
  }
}
let animal = new Animal(); //直接报错 无法创建抽象类的实例
let cat = new Cat();  // 派生类可以被实例化
cat.speak();

思考 1:重写(override)和重载(overload)的区别

重写是指子类重写继承自父类中的方法 重载是指为同一个函数提供多个类型定义

class Animal {
  speak(word: string): string {
    return "动物:" + word;
  }
}
class Cat extends Animal {
  speak(word: string): string {
    return "猫:" + word;
  }
}
let cat = new Cat();
console.log(cat.speak("hello"));
// 上面是重写
//--------------------------------------------
// 下面是重载
function double(val: number): number;
function double(val: string): string;
function double(val: any): any {
  if (typeof val == "number") {
    return val * 2;
  }
  return val + val;
}

let r = double(1);
console.log(r);

思考 2:什么是多态

在父类中定义一个方法,在子类中有多个实现,在程序运行的时候,根据不同的对象执行不同的操作,实现运行时的绑定。

abstract class Animal {
  // 声明抽象的方法,让子类去实现
  abstract sleep(): void;
}
class Dog extends Animal {
  sleep() {
    console.log("dog sleep");
  }
}
let dog = new Dog();
class Cat extends Animal {
  sleep() {
    console.log("cat sleep");
  }
}
let cat = new Cat();
let animals: Animal[] = [dog, cat];
animals.forEach((i) => {
  i.sleep();
});

8. class关于implements和extends的区别

extends 用于继承一个已经存在的类。继承后,子类将拥有父类除了构造函数以外的所有方法和属性。
implements 用于实现一个或多个接口的方法。接口是一种结构,它定义了一个类应该包含哪些方法,但并没有实现这些方法。

相同点:两者都可以实现父类,减少代码,而且面向对象特征。

区别:
implements: 实现父类,子类不可以覆盖父类的方法或者变量。即使子类定义与父类相同的变量或者函数,也会被父类取代掉。
extends: 可以实现父类,也可以调用父类初始化 this.parent()。而且会覆盖父类定义的变量或者函数。

class Animal {
   constructor(name) {
       this.name = name;
   }
   speak() {
       console.log(this.name + ' is speaking');
   }
}

class Dog extends Animal {
   bark() {
       console.log(this.name + ' is barking');
   }
}

let dog = new Dog('Rex');
dog.bark(); // Rex is barking
dog.speak(); // Rex is speaking
interface IPet {
    speak();
}
 
class Dog implements IPet {
    speak() {
        console.log('Woof!');
    }
}
 
let dog = new Dog();
dog.speak(); // Woof!

十二、接口

接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,接口只定义对象的结构,而不考虑实际值, 对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口(就是使类满足接口的要求)。

1. 对象的形状

使用接口约束的时候不能多一个属性也不能少一个属性

  // 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
 // 同时接口也可以当成类型声明去使用
interface Speakable {
  speak(): void;
  readonly lng: string; //readonly表示只读属性 后续不可以更改
  name?: string; //?表示可选属性
}

let speakman: Speakable = {
  //   speak() {}, //少属性会报错
  name: "hello",
  lng: "en",
  age: 111, //多属性也会报错
};

// 正确案例
interface Arrobj{
    name:string,
    age:number
}
let arr:Arrobj[]=[{name:'jimmy',age:22}]

2. 行为的抽象

接口可以把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类

一个类可以实现多个接口,一个接口也可以被多个类实现

我们用 implements关键字来代表 实现

//接口可以在面向对象编程中表示为行为的抽象
interface Speakable {
  speak(): void;
}
interface Eatable {
  eat(): void;
}
//一个类可以实现多个接口
class Person implements Speakable, Eatable {
  speak() {
    console.log("Person说话");
  }
  // eat() {} //需要实现的接口包含eat方法 不实现会报错
}

3. 定义任意属性

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

如果我们在定义接口的时候无法预先知道有哪些属性的时候,可以使用 [propName:string]:any,propName 名字是任意的

interface Person {
   id: number;
   name: string;
   [propName: string]: any;
 }

 let p1 = {
   id: 1,
   name: "hello",
   age: 10,
 };
// 这个接口表示 必须要有 id 和 name 这两个字段 然后还可以新加其余的未知字段

4. 接口的继承

我们除了类可以继承 接口也可以继承 同样的使用 extends关键字

interface Speakable {
  speak(): void;
}
interface SpeakChinese extends Speakable {
  speakChinese(): void;
}
class Person implements SpeakChinese {
  speak() {
    console.log("Person");
  }
  speakChinese() {
    console.log("speakChinese");
  }
}

5. 接口的合并

重名interface 可以合并

//重名interface  可以合并
interface A{name:string}
interface A{age:number}
var x:A={name:'xx',age:20}
var x1:A={age:20}  // 类型 "{ age: number; }" 中缺少属性 "name",但类型 "A" 中需要该属性

思考:接口和类型别名的区别

实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。

1.基础数据类型 与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组

// primitive
type Name = string;

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

2.重复定义

接口可以定义多次 会被自动合并为单个接口 类型别名不可以重复定义

type myType = {
    name: string,
    age: number
};

// 重复标识符“myType”
type myType = {
    name: string,
    age: number
}; 

interface Point {
  x: number;
}
interface Point {
  y: number;
}
const point: Point = { x: 1, y: 2 };

3.扩展 接口可以扩展类型别名,同理,类型别名也可以扩展接口。但是两者实现扩展的方式不同

接口的扩展就是继承,通过 extends 来实现。类型别名的扩展就是交叉类型,通过 & 来实现。

// 接口扩展接口
interface PointX {
  x: number;
}

interface Point extends PointX {
  y: number;
}
// -------------------------------------
// 类型别名扩展类型别名
type PointY = {
  x: number;
};

type Point1 = PointY & {
  y: number;
};

// -------------------------------------
// 接口扩展类型别名
type PointZ = {
  x: number;
};
interface Point2 extends PointZ {
  y: number;
}
// -------------------------------------
// 类型别名扩展接口
interface PointG {
  x: number;
}
type Point3 = PointG & {
  y: number;
};

4.实现 这里有一个特殊情况 类无法实现定义了联合类型的类型别名

type PartialPoint = { x: number } | { y: number };

//  类只能实现对象类型或对象类型与静态已知成员的交集
class SomePartialPoint implements PartialPoint {
  // Error
  x = 1;
  y = 2;
}

6. 接口和抽象类的区别

1. 相同点

  1. 抽象类和接口都不能被实例化。
  2. 继承抽象类和实现接口都要对其中的抽象方法进行全部实现。

2. 不同点

  1. 接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。
  2. 接口中只能包含抽象方法;而抽象类中可以包含普通方法和抽象方法。
  3. 接口中不能定义静态方法;而抽象类可以定义静态方法。
  4. 接口中只能定义静态常量,不能定义普通属性;而抽象类既 可以定义普通属性,也可以定义静态常量属性。
  5. 接口不包含构造器;而抽象类中可以包含构造器。抽象类中的构造器并不是用于创建对象,而是让子类调用这些构造器来完成属于抽象类的初始化工作。
  6. 接口中不包含初始化块;而抽象类可以包含初始化块。
  7. 一个类最多有一个直接父类,包括抽象类;但是一个类可以直接实现多个接口。
  8. 接口只可以继承一个或多个其它接口(implements);抽象类可以继承一个类和实现多个接口(extends)。
  9. 接口方法默认修饰符是public,可以使用其它修饰符。而抽象方法可以有public、protected和private这些修饰符。
  10. 接口完全是抽象的,它根本不存在方法的实现;而抽象类可以有默认的方法实现;
  11. 接口强调特定功能的实现;而抽象类强调所属关系。

7. 定义函数类型

interface Fn {
  (name: string): number[];
}

const fn: Fn = function (name: string) {
  return [1];
};

十三、泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

为了更好的了解泛型的作用 我们可以看下面的一个例子


function createArray(length: number, value: any): any[] {
  let result = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

console.log(createArray(3, "x")); // ['x', 'x', 'x']

上述这段代码用来生成一个长度为 length 值为 value 的数组 但是我们其实可以发现一个问题 不管我们传入什么类型的 value 返回值的数组永远是 any 类型 如果我们想要的效果是 我们预先不知道会传入什么类型 但是我们希望不管我们传入什么类型 我们的返回的数组的指里面的类型应该和参数保持一致 那么这时候 泛型就登场了

使用泛型改造

function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

console.log(createArray<string>(3, "x")); // ['x', 'x', 'x']

我们可以使用<>的写法 然后再面传入一个变量 T 用来表示后续函数需要用到的类型 当我们真正去调用函数的时候再传入 T 的类型就可以解决很多预先无法确定类型相关的问题

1. 泛型应用场景

  1. 声明接口
  2. 泛型使用场景:在 定义 函数、接口、类 的时候,不能预先确定要使用的数据的类型,而是 在使用的时候才能确定
  3. 将类型作为变量使用,即 动态类型

2. 类型别名定义泛型

type A<T>  = string | number | T

let a:A<null> = null
let a1:A<boolean> = true
let a2:A<number> = 2

3. 接口定义泛型

interface Data<T> {
    msg: T
}

let data:Data<number> = {
    msg: 1
}
let data1:Data<boolean> = {
    msg: true
}

4. 多个类型参数

如果我们需要有多个未知的类型占位 那么我们可以定义任何的字母来表示不同的类型参数

function sum<T,K>(a: T, b:K): Array<T | K>  {
    return [a,b]
}
 
sum(1, true)
sum('1', 4)


function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}

console.log(swap([7, "seven"])); // ['seven', 7]

5. 泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// Property 'length' does not exist on type 'T'.

上例中,泛型 T 不一定包含属性 length,所以编译的时候报错了。

这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束

interface Lengthwise {
  length: number;
}

// T extends Lengthwise 表示泛型T必须是Lengthwise实现类(子类)
function loggingIdentity<T extends Lengthwise>(arg: T): number {
  return arg.length;
}

loggingIdentity('3')
loggingIdentity(3)  // 报错

注意:我们在泛型里面使用extends关键字代表的是泛型约束 需要和类的继承区分开

6. 使用keyof 约束对象

其中使用了TS泛型和泛型约束。首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型

 function prop<T, K extends keyof T>(obj: T, key: K) {
    return obj[key]
 }
  
  
 let o = { a: 1, b: 2, c: 3 }
  
 prop(o, 'a') 
 prop(o, 'd') //此时就会报错发现找不到




 let obj = {
    name: '小满',
    age: 12
 }

 type key = keyof typeof obj  // type key = "age" | "name"

// 利用extends关键字约束 K类型必须为keyof T联合类型的子类型
 function ob<T extends  object, K extends keyof T>(obj:T,key:K){
    return obj[key]
 }
 ob(obj, 'age')
 ob(obj, 'sex') // 报错

7.泛型接口

定义接口的时候也可以指定泛型

interface Cart<T> {
  list: T[];
}
let cart: Cart<{ name: string; price: number }> = {
  list: [{ name: "hello", price: 10 }],
};
console.log(cart.list[0].name, cart.list[0].price);

我们定义了接口传入的类型 T 之后返回的对象数组里面 T 就是当时传入的参数类型

8. 泛型类

class Test<T>{
    name: T;
    constructor(name: T) {
        this.name = name;
    }
}

const test = new Test<string>('猴子');
console.log(test)

9. 泛型类型别名

type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

10. 泛型参数的默认类型

我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用

function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
console.log(createArray(2,'d'))

11. 泛型变量

对刚接触 TypeScript 泛型的小伙伴来说,看到 T 和 E,还有 K 和 V 这些泛型变量时,估计会一脸懵逼。其实这些大写字母并没有什么本质的区别,只不过是一个约定好的规范而已。也就是说使用大写字母 A-Z 定义的类型变量都属于泛型,把 T 换成 A,也是一样的。下面我们介绍一下一些常见泛型变量代表的意思:

  • T(Type):表示一个 TypeScript 类型
  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素类型

12. 扩展

循环遍历

 interface Data {
    name: string,
    age: number,
    sex: string
 }


 // for in   for(let key in obj)
 type Options<T extends object> = {
    readonly [key in keyof T]:T[key]
 }

 type B = Options<Data>

十四、namespace命名空间

我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace 避免这个问题出现

内部模块,主要用于组织代码,避免命名冲突。
命名空间内的类默认私有
通过 export 暴露
通过 namespace 关键字定义
命名空间的用法、嵌套、到处、简化、合并

1. 导出

namespace 所有的变量以及方法必须导出才能访问
如果不用export 导出是无法读取其值的

namespace Test {
  export let a = 1;
  export const add = (a: number, b: number) => a + b;
}

console.log(Test.a) // 1
console.log(Test.add(1,2)) // 3

2. 嵌套命名空间

namespace Test {
  export namespace Test2 {
    export let a = 1;
    export const add = (a: number, b: number) => a + b;
  }
}

console.log(Test.Test2.add(1,2)) // 3

3. 合并

namespace Test {
    export let a = 1;
    export const add = (a: number, b: number) => a + b;
}


namespace Test {
    export let b = 2;
}

console.log(Test.a) // 1
console.log(Test.b) // 2
console.log(Test.add(1,2)) // 3

4. 抽离命名空间

a.ts

export namespace V {
    export const a = 1
}

b.ts

import {V} from '../observer/index'
 
console.log(V);

5. 简化命名空间

namespace A  {
  export namespace B {
      export const C = 1
  }
}
import X = A.B.C
console.log(X); // 1


import { Test } from "./b";
import a = Test.add
console.log(a(1,2))

十五、内置对象

ECMAScript 的内置对象

Boolean、Number、string、RegExp、Date、Error

let b: Boolean = new Boolean(1)
let n: Number = new Number(true)
let s: String = new String('测试')
let d: Date = new Date()
let r: RegExp = new RegExp(/\w/)
let e: Error = new Error("error!")
let h:XMLHttpRequest = new XMLHttpRequest()

DOM 内置对象

Document、HTMLElement、Event、NodeList 等

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll("div");
let all: NodeListOf<Element> = document.querySelectorAll("div footer");
let all1: NodeListOf<HTMLDivElement | HTMLElement> = document.querySelectorAll("div footer");
//读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div: HTMLElement = document.querySelector("div") as HTMLDivElement;
document.addEventListener('click', function (e: MouseEvent) {
    
});

// dom元素的类型: HTML(元素名称)Element | HTMLElement | Element
let div1: HTMLDivElement = document.querySelector("div");
let input: HTMLInputElement = document.querySelector("input");
let canvas: HTMLCanvasElement = document.querySelector("canvas");
let header: HTMLElement = document.querySelector("header");
let footer: HTMLElement = document.querySelector("footer");
let div2 = document.querySelector("div") as Element;


//dom元素的映射表
interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "abbr": HTMLElement;
    "address": HTMLElement;
    "area": HTMLAreaElement;
    "article": HTMLElement;
    "aside": HTMLElement;
    "audio": HTMLAudioElement;
    "b": HTMLElement;
    "base": HTMLBaseElement;
    "bdi": HTMLElement;
    "bdo": HTMLElement;
    "blockquote": HTMLQuoteElement;
    "body": HTMLBodyElement;
    "br": HTMLBRElement;
    "button": HTMLButtonElement;
    "canvas": HTMLCanvasElement;
    "caption": HTMLTableCaptionElement;
    "cite": HTMLElement;
    "code": HTMLElement;
    "col": HTMLTableColElement;
    "colgroup": HTMLTableColElement;
    "data": HTMLDataElement;
    "datalist": HTMLDataListElement;
    "dd": HTMLElement;
    "del": HTMLModElement;
    "details": HTMLDetailsElement;
    "dfn": HTMLElement;
    "dialog": HTMLDialogElement;
    "dir": HTMLDirectoryElement;
    "div": HTMLDivElement;
    "dl": HTMLDListElement;
    "dt": HTMLElement;
    "em": HTMLElement;
    "embed": HTMLEmbedElement;
    "fieldset": HTMLFieldSetElement;
    "figcaption": HTMLElement;
    "figure": HTMLElement;
    "font": HTMLFontElement;
    "footer": HTMLElement;
    "form": HTMLFormElement;
    "frame": HTMLFrameElement;
    "frameset": HTMLFrameSetElement;
    "h1": HTMLHeadingElement;
    "h2": HTMLHeadingElement;
    "h3": HTMLHeadingElement;
    "h4": HTMLHeadingElement;
    "h5": HTMLHeadingElement;
    "h6": HTMLHeadingElement;
    "head": HTMLHeadElement;
    "header": HTMLElement;
    "hgroup": HTMLElement;
    "hr": HTMLHRElement;
    "html": HTMLHtmlElement;
    "i": HTMLElement;
    "iframe": HTMLIFrameElement;
    "img": HTMLImageElement;
    "input": HTMLInputElement;
    "ins": HTMLModElement;
    "kbd": HTMLElement;
    "label": HTMLLabelElement;
    "legend": HTMLLegendElement;
    "li": HTMLLIElement;
    "link": HTMLLinkElement;
    "main": HTMLElement;
    "map": HTMLMapElement;
    "mark": HTMLElement;
    "marquee": HTMLMarqueeElement;
    "menu": HTMLMenuElement;
    "meta": HTMLMetaElement;
    "meter": HTMLMeterElement;
    "nav": HTMLElement;
    "noscript": HTMLElement;
    "object": HTMLObjectElement;
    "ol": HTMLOListElement;
    "optgroup": HTMLOptGroupElement;
    "option": HTMLOptionElement;
    "output": HTMLOutputElement;
    "p": HTMLParagraphElement;
    "param": HTMLParamElement;
    "picture": HTMLPictureElement;
    "pre": HTMLPreElement;
    "progress": HTMLProgressElement;
    "q": HTMLQuoteElement;
    "rp": HTMLElement;
    "rt": HTMLElement;
    "ruby": HTMLElement;
    "s": HTMLElement;
    "samp": HTMLElement;
    "script": HTMLScriptElement;
    "section": HTMLElement;
    "select": HTMLSelectElement;
    "slot": HTMLSlotElement;
    "small": HTMLElement;
    "source": HTMLSourceElement;
    "span": HTMLSpanElement;
    "strong": HTMLElement;
    "style": HTMLStyleElement;
    "sub": HTMLElement;
    "summary": HTMLElement;
    "sup": HTMLElement;
    "table": HTMLTableElement;
    "tbody": HTMLTableSectionElement;
    "td": HTMLTableDataCellElement;
    "template": HTMLTemplateElement;
    "textarea": HTMLTextAreaElement;
    "tfoot": HTMLTableSectionElement;
    "th": HTMLTableHeaderCellElement;
    "thead": HTMLTableSectionElement;
    "time": HTMLTimeElement;
    "title": HTMLTitleElement;
    "tr": HTMLTableRowElement;
    "track": HTMLTrackElement;
    "u": HTMLElement;
    "ul": HTMLUListElement;
    "var": HTMLElement;
    "video": HTMLVideoElement;
    "wbr": HTMLElement;
}

BOM 的内置对象

let local: Storage = localStorage;
let lo: Location = location;
let promise: Promise<number> = new Promise((resolve, reject) => {
  resolve(1);
});
let cookie: string = document.cookie;

十六、编译

1. 编译文件

  1. 自动编译文件
    编译文件时,使用 -w 指令后,TS编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。
 tsc index.ts -w
  1. 自动编译整个项目

    • 如果直接使用tsc指令,则可以自动将当前项目下的所有ts文件编译为js文件。
    • 但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件 tsconfig.json
    • tsconfig.json是一个JSON文件,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译

2. tsconfig.json 的作用

  • 用于标识 TypeScript 项目的根路径;
  • 用于配置 TypeScript 编译器;
  • 用于指定编译的文件。

3. tsconfig.json 重要字段

  • files - 设置要编译的文件的名称;
  • include - 设置需要进行编译的文件,支持路径模式匹配;
  • exclude - 设置无需进行编译的文件,支持路径模式匹配;
  • compilerOptions - 设置与编译流程相关的选项。
{
  /*
  tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译
    "include" 用来指定哪些ts文件需要被编译
      路径:** 表示任意目录
            * 表示任意文件
    "exclude" 不需要被编译的文件目录
        默认值:["node_modules", "bower_components", "jspm_packages"]

*/
  "include": [
    "./src/**/*"
  ],
  "exclude": [node_modules],
  "files": [
    "core.ts",
    "tsc.ts"
  ],
  /*
    compilerOptions 编译器的选项
  */
  "compilerOptions": {}
}

4. compilerOptions 选项

compilerOptions 支持很多选项,常见的有 baseUrltargetbaseUrlmoduleResolutionlib 等。

compilerOptions 每个选项的详细说明如下:

{
  "compilerOptions": {
    "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
    "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
    "diagnostics": true, // 打印诊断信息 
    "target": "ES5", // 目标语言的版本
    "module": "CommonJS", // 生成代码的模板标准
    "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
    "lib": [
      "DOM",
      "ES2015",
      "ScriptHost",
      "ES2019.Array"
    ], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
    "allowJS": true, // 允许编译器编译JS,JSX文件
    "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
    "jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "outDir": "./dist", // 指定输出目录
    "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
    "declaration": true, // 生成声明文件,开启后会自动生成声明文件
    "declarationDir": "./file", // 指定生成声明文件存放目录
    "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
    "sourceMap": true, // 生成目标文件的sourceMap文件
    "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
    "declarationMap": true, // 为声明文件生成sourceMap
    "typeRoots": [], // 声明文件目录,默认时node_modules/@types
    "types": [], // 加载的声明文件包
    "isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
    "removeComments": true, // 删除注释 
    "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
    "noEmitOnError": true, // 发送错误时不输出任何文件
    "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
    "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
    "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
    "strict": true, // 开启所有严格的类型检查
    "alwaysStrict": true, // 在代码中注入'use strict'
    "noImplicitAny": true, // 不允许隐式的any类型
    "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
    "strictFunctionTypes": true, // 不允许函数参数双向协变
    "strictPropertyInitialization": true, // 类的实例属性必须初始化
    "strictBindCallApply": true, // 严格的bind/call/apply检查
    "noImplicitThis": true, // 不允许this有隐式的any类型
    "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
    "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
    "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
    "noImplicitReturns": true, //每个分支都会有返回值
    "esModuleInterop": true, // 允许export=导出,由import from 导入
    "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
    "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
    "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
    "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
    "paths": { // 路径映射,相对于baseUrl
      // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
      "jquery": [
        "node_modules/jquery/dist/jquery.min.js"
      ]
    },
    "inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
    "rootDirs": [
      "src",
      "out"
    ], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
    "listEmittedFiles": true, // 打印输出文件
    "sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
    "experimentalDecorators": true, // 启用装饰器
    "emitDecoratorMetadata": true, // 为装饰器提供元数据的支持
    "listFiles": true // 打印编译的文件(包括引用的声明文件)
    
  }
  // 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
  "include": [
    "src/**/*"
  ],
  // 指定一个排除列表(include的反向操作)
  "exclude": [
    "demo.ts"
  ],
  // 指定哪些文件使用该配置(属于手动一个个指定文件)
  "files": [
    "demo.ts"
  ]
}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值