(十一)元祖
越界后,会推断为一个联合类型,比如[number,string] , 越界后为 number|string 类型。
可以只读,readonly
可选,可以命名,用?表示可选。
let arr:[number,boolean] = [1,false]
arr[0] = 345
// arr[0] = '234' // 错误
arr.push(234)
// arr.push('234') // 错误
arr = [34,true]
let arr1: readonly [number,boolean] = [1,false]
// arr1[0] = 4567 // 错误
arr1 = [65,false] // 可以
// arr1.push(234) // 错误
let arr2: [x:number,y?:boolean] = [4]
// 读到元祖的类型
type first = typeof arr[0] // number
type two = typeof arr['length'] //2
(十二)枚举 enum
默认从0开始,对象的方式输出
指定初始值,比如1,后续会递增。
也可以自定义指定具体的值 如:red = 1 green = 3
也可以使用字符串自定义 如:red = ‘red’ green = 'green'
也可以穿插使用 如: red = 2 green = ‘gn’
const关键字创建enum的话,与常规枚举的区别在于,在编译时会将其内联,而不会在运行时创建对象。这意味着无法在运行时使用const
枚举的属性名称来访问枚举的值。不能反向映射。
// 默认从0开始的,对象的方式输出
enum Color{
red,
green,
blue
}
// 增长枚举,后续会递增
enum Color1{
red = 1,
green,
blue
}
// 自定义枚举,使用=
enum Color2{
red = 1,
green = 5,
blue = 6
}
// 字符串枚举
enum Color3{
red = 'red',
green = 'green',
blue = 'blue'
}
// 穿插使用,也支持
enum Isok{
yes = 1,
no = 'no'
}
const enum Types{
fail,
success
}
let code = 0
if(code == Types.fail){}
// 这里编译,会将 Types.fail 直接编译为常量(这里为0),而不加const,会编译为一个对象
// 反向映射,可以key读value值,也可以value读key值
enum Types2{
fail,
success
}
let success:number = Types2.success
let key = Types2[success]
console.log(`value:${success}, key:${key}`);
(十三)type 类型推论,类型别名
类型推论:ts自己进行变量的推论类型,如果没有赋值,默认为any
可以给类型起别名,关键字type
type和interface的区别:1,interface可以extends继承 2,interface必须是属性定义,不能省略直接定义 3, interface遇到重名的,会自己合并,type不可以
!!!extends在type中是包含的意思。
type num = 1 extends number ? 1 : 0 // 1 左边作为右边类型的子类型。
type s = string
type s1 = string & number
type sn = string | number
type fn = ()=>void
type ar = []
type ob = {name:string}
let str:s = '信息'
// extends 在type中是包含的意思,
// 左边的值会作为右边类型的子类型。
// 记住类型的顶级顺序,never在最底层,不包含1.
type num = 1 extends number ? 1 : 0 // 1
type num1 = 1 extends any ? 1 : 0 // 1
type num2 = 1 extends unknown ? 1 : 0 // 1
type num3 = 1 extends Object ? 1 : 0 // 1
type num4 = 1 extends Number ? 1 : 0 // 1
type num5 = 1 extends never ? 1 : 0 // 0
(十四)never
通常表示不存在状态,无法达到预期
type A = string & number // never
可以作为兜底逻辑,无法被赋值为某个类型,所以会出现提示,避免出错
type A = string & number // never
// 一执行就报错,所以never更合理
function xx(): never{
throw new Error('执行错误')
}
// 写在联合类型中,never会直接被忽略
type B = string | number | never
type K = '唱' | '跳' | 'rap'
function kun(value:K){
switch(value){
case '唱':
break
case '跳':
break
case 'rap':
break
default:
// 兜底逻辑 (无法被赋值string,会出现提示,所以避免错误发生)
const error:never = value
break
}
}
(十五)Symbol
Symbol 是个全局的方法。参数 string|number ,空值
表示唯一性,即使完全一样,返回的值也是唯一性的(内存地址不同)
那什么时候来个symbol可以返回true呢,使用Symbol.for(xxx) 来判断。
!!!在对象种使用Symbol的话,如何读到symbol,
for in 读不到symbol, Object.keys() 读不到symbol,Object.getOwnPropertyNames() 读不到symbol,Object.getOwnPropertySymbols() 只能读到只读的symbol,无法读到其他。
那如何基础类型和symbol都能读到呢, Reflect,映射。 Reflect.ownKeys().
let a1:symbol = Symbol(1)
let a2:symbol = Symbol(1)
console.log(a1,a2,a1==a2); // Symbol(1) Symbol(1) false
// for 和 Symbol有区别,for会在全局中查询是否注册过key值,如果有直接拿来用,如果没有就创建一个
console.log(Symbol.for('xiao') === Symbol.for('xiao')); // true
// 可以确保key值的唯一性
let obj = {
name:'11',
[a1]:'33',
[a2]:'45'
}
// for in 的问题:这里只能读取到name,无法读到symbol
for(let key in obj){
console.log(key);
}
// Object.keys() 也读取不到symbol
console.log(Object.keys(obj));
// Object.getOwnPropertyNames() 也读取不到symbol
console.log(Object.getOwnPropertyNames(obj));
// Object.getOwnPropertySymbols() 可以读取不到symbol,但只能读到到symbol,不包含其他
console.log(Object.getOwnPropertySymbols(obj));
// 如何symbol和基础类型都取值到,使用Reflect,映射
console.log(Reflect.ownKeys(obj));
(十六)生成器,迭代器
生成器写法: function *A(){ yield xxx }
按照顺序,next返回都执行一次,done:true 表示没有东西可以迭代了,判断是否输出完毕了。
迭代器 set ,map
set天然去重,数字,字符串,不能是对象。
map 键值对形式,key可以是引用类型。
迭代器作用:可以遍历伪数组,遍历可迭代的。Symbol.iterator
for of是迭代器的语法糖, for of是不能迭代对象的,没有iterator,解构的原理也是iterator。
// 生成器
function* gen() {
yield '小心' // 同步异步
yield Promise.resolve('花卉')
yield '中啦'
yield '刀哥'
}
// 按照顺序的,每次next返回都执行一次
// done:true 表示没有东西可以迭代了。
// 可以通过done的值来判断 生成器是否输出完毕了。
const man = gen() // Generator
console.log(man.next()); // { value: '小心', done: false }
console.log(man.next()); // { value: Promise { '花卉' }, done: false }
console.log(man.next());
console.log(man.next());
console.log(man.next()); // { value: undefined, done: true }
// 迭代器
// set map
let set: Set<number> = new Set([1, 1, 1, 2, 2, 3, 4]) // 天然去重的, 只能是数字,字符串,对象不支持
console.log(set); // Set(4) { 1, 2, 3, 4 }
let map: Map<any, any> = new Map()
let arr = [1, 2, 3]
map.set(arr, 'ddd') // map的key值可以使用引用类型。
console.log(map.get(arr));
function args() {
console.log(arguments); // 伪数组
}
// let list = document.querySelectorAll('div') // 伪数组,不过可以使用foreach
// 有什么方法可以同时遍历以上类型的呢 -- 迭代器 -- Symbol.iterator
const each = (value: any) => {
let It: any = value[Symbol.iterator]()
let next: any = { done: false }
while (!next.done) {
next = It.next()
if (!next.done) {
console.log(next.value);
}
}
}
// each(map) // [ [ 1, 2, 3 ], 'ddd' ] key和value的数组
// 迭代器的语法糖 for of
// 注意:for of 对象是不能用的,没有iterator
// 解构的底层原理,也是调用 iterator 的
for (let value of map) {
console.log(value);
}
// 对象 支持 for of 写法
let obj = {
max: 4,
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
}
}
}
}
}
}
for (let value of obj) {
console.log(value);
}
// 解构 - null 和 undefined 不能被解构
let x = [...obj]
console.log(x); //[ 0, 1, 2, 3 ]
let y = {...obj}
console.log(y);
// {
// max: 4,
// current: 0,
// [Symbol(Symbol.iterator)]: [Function: [Symbol.iterator]]
// }
(十七) 泛型
动态类型,避免因为类型不同出现的重复性工作,将类型变成动态的。
写法: 自定义命名,在名字后面,<>里定义,如:function da<T>(a:T,b:T): Array<T>{}
泛型可以是多个,也可以使用默认值。arr<T=number,K=number>(a:T,b:K){}
// 写法 自定义命名,在名字后面,<>里自定义
function da<T>(a:T,b:T):Array<T>{
return [a,b]
}
da(1,2)
da('1','3')
da(false,true)
type A<T> = string | number | T
let a:A<boolean> = false
let b:A<undefined> = undefined
let c:A<null> = null
interface Data<T>{
msg:T
}
let data:Data<string> = {
msg:'dddddddddd'
}
// 泛型也可以支持多个, 也可以使用默认值
function arr<T=number,K=number>(a:T,b:K):Array<T | K>{
return [a,b]
}
const axios = {
get<T>(url:string):Promise<T>{
return new Promise((resolve,reject)=>{
let xhr: XMLHttpRequest = new XMLHttpRequest() // node环境不行,编译为js
xhr.open('GET',url)
xhr.onreadystatechange = ()=>{
if(xhr.readyState == 4 && xhr.status == 200){
resolve(JSON.parse(xhr.responseText))
}
}
xhr.send(null)
})
}
}
interface Res{
message:string
code:number
}
axios.get<Res>('./json/data.json').then(res=>{
console.log(res);
})
(十八)泛型约束
<T extends number> 约束泛型的范围 在extends后面跟一个约束类型。
keyof 会将类型视为联合类型。
let obj = {
name:'小',
sex:'女'
}
type key = keyof typeof obj // "name" | "sex"
约束取值,这样更加安全。
for(let key in obj) 相当于循环操作。
function add<T extends number>(a:T,b:T){
return a+b
}
add(1,3)
interface Len{
length:number
}
function fn<T extends Len>(a:T){
console.log(a.length);
}
function ob<T extends object,K extends keyof T>(obj:T,key:K){
return obj[key] // 约束取值,认为安全
}
ob(obj,'name')
ob(obj,'sex')
// 使用工具实现属性可选
interface Pat{
name:string
age:number
sex:string
}
// for in for(let key in obj) 相当于循环操作
type Opt<T extends object> = {
[key in keyof T]?: T[key]
}
type Opt2<T extends object> = {
readonly [key in keyof T]: T[key]
}
type B = Opt<Pat>
type C = Opt2<Pat>
(十九)namespace 命名空间
ts和es2015一样,包含顶级import或者export的文件才会视为一个模块。
相反的,如果没有顶级的import或者export就会被视为全局可见了,那变量命名就容易出现冲突。
所以出现命名空间,相互独立。
namespace里,需要export才能被外界访问。 可以嵌套,可以抽离为一个文件,引入。可以简化,别名。如果重名,那么属性会合并。
namespace A{
export const a = 1 // 命名空间,需要export才能在外界访问
}
// 可以嵌套
namespace B{
export namespace inner{
export const a = 3
}
}
console.log(A.a);
console.log(B.inner.a);
// 可以抽离为一个文件,引入
import { IN2 } from "./index2";
console.log(IN2.ab);
// 简化,别名
import BB = B.inner
console.log(BB.a);
// 如果重名,那么属性会合并
(旧版的ts-node不识别,需要编译之后查看,新版的ts-node可以识别了。)
(二十)三斜线指令
///<reference path='xxxx'/> 声明文件的依赖,只能在文件的最顶端,单行注释。
引入后,可以使用里面的变量,类似于export,import。
还可以指定声明文件:/// <reference types='node'/>
/// <reference path='./index2.ts'/>
/// <reference path='./index3.ts'/>
/// <reference types='node'/>
/*
三斜线指令
/// <reference path="..."/> 声明文件间的依赖
只能在文件最顶端,单行注释。
引入后,可以使用里面的变量。类似于export,import
还可以指定声明文件:/// <reference types='node'/>
*/
namespace A{
export const a = 45
}
console.log(B.b);
console.log(A.c);
console.log(A.a);