JS类型-TS扩展类型实例
Js类型有哪些问题? JS的超集TS又有哪些扩展呢?
# 类型安全:强类型与弱类型
强类型 - 语言层面限制实参与形参类型相同
弱类型 - 不限制实参与形参类型
# 类型转换:静态类型与动态类型
静态类型 - 变量声明是类型明确,之后就不允许修改
动态类型 - 运行时才能确定变量类型,并且类型可以变化
var x = 100
x = '100'
console.log(x) // '100'
# JS自有类型的问题
JS是弱类型且动态类型的语言,几乎没有类型限制,比较随意。这种结果在当下算是问题,不可靠甚至是坑。 但在当时却是优点。
JS在设计之初,处理的问题并没有现在这么复杂,这样的随意和灵活非常容易上手,写完就能用,却是是很大优势。 而且,JS是脚本语言,没有编译环节,所以也就没有必要去做静态编译阶段要做的类型检查了。
到了现在,js面对如此庞大的项目,万物皆可js的当下,这些优势就都变成了短板。
# 弱类型的问题
let obj = {}
console.log(obj.x.y())
上面这么写完全没问题,而且必须要到运行时执行到才会报错。
function sum(x, y) {
return x + y
}
console.log(1, 2) // 3
console.log(1, '2') // '12'
上面这个函数的求和原有意义就很不明朗了。
以上如果是强类型语言,在语法检查或编译时就会发现问题,而不会留到运行出错时造成问题才发现了。
# 强类型优势
- 错误更早暴露
- 编码更智能,编码会更准确
- 重构更可靠
- 减少必要的类型判断
# Flow
Flow是JS的类型检查器,他通过让我们在开发时,增加对变量和参数的类型注解来检查代码。
function sum(a: number, b: number) {
return a + b
}
console.log(sum(1, '2')) // flow检查不通过
最终这些注解会在babel等工具的处理去除,并不会影响上线代码。
# TS
TS是JS的超集;TS是在原有JS基础上扩展的,扩展了类型系统和ES6的新特性,最终再会编译为JS; 目前主流项目均已转入TS开发,第三方库包括我们实际的项目都在逐步切换。
可以说,TS已经是主流。
# 原始类型
// 原始数据类型
const x: string = '1212'
const y: number = 12 // NaN Infinity
const z: boolean = true // false
const a: void = undefined // 常用于函数返回值
const b: null = null
const c: undefined = undefined
const d: symbol = Symbol()
# 对象类型
// 这里object并不单指对象,而是除了原始对象外的其他类型
const obj1: object = [] // {},function(){}
// 下面这种才是定义普通对象的方式,但更常用的是interface的形式
const obj2: {} = {}
const obj3: { name: string } = {name: 'jack'}
# 数组类型
// 两种方式均可
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
# 元组类型
这是一种特殊数据结构,他是一个明确元素数量和类型的数组,内部的元素类型不一定相同
const tuple: [number, string] = [11, 'abc']
const tuple2: [number, string] = [11, 'abc', true] // 这会报错
const tuple3 = Object.entries({
x: 1, y: 2
}) // 返回值就是类型为 [string,number] 的元组
# 枚举类型
// 枚举值可以不指定,默认从0累计,也可以指定字符串
enum Status {
Read = 0,
Unread = 1,
Delete = 2,
}
const post = {
name: 'xxxx',
status: Status.Read
}
枚举类型不一样的地方在于,他最终会侵入我们编写的代码,我们可以看看最终编译的结果。
var Status;
(function (Status) {
Status[Status["Read"] = 0] = "Read";
Status[Status["Unread"] = 1] = "Unread";
Status[Status["Delete"] = 2] = "Delete";
})(Status || (Status = {}));
/**
* {
"0": "Read",
"1": "Unread",
"2": "Delete",
"Read": 0,
"Unread": 1,
"Delete": 2
}
*/
var post = {
name: 'xxxx',
status: Status.Read
};
可以看出,他是一个双向键值对象。他既可以通过键获取值,也可以通过值获取键。
# 函数类型
function sum(a: number, b: number): number {
return a + b
}
sum(1, 2)
sum(1, 2, 3) // 报错参数个数需要一致
// 不定参数长度
function sum(a: number, b: number, ...rest: number[]): number {
return a + b + rest.length
}
sum(1, 2, 3)
const fun: (a: number, b: number) => number = function (a, b) {
return a + b
}
# 任意类型
// 这里的参数可以接受任意类型
function stringify(val: any): string {
return JSON.stringify(val)
}
stringify(1)
stringify('11')
stringify(true)
let x: any = 1
x = true
x.y = 12
为了兼容老代码需要使用any,但轻易不要用,因为这和js代码一样,没有了约束,就没有了类型系统但优势。
# 类型推断/类型断言
ts可以根据上下文做类型推断
let x = 19;
x = '212' // 报错,因为推断为x为number类型
类型断言是说,ts在某些情况下无法推断具体类型,但开发者知道是什么类型,这时候我们就可以通过类型断言来指定类型。
const nums = [1, 2, 3, 4, 5, 5, 6];
// 推断返回值为number|undefined,因为ts认为可能找不到
const finder = nums.find(x => x === 1);
// 作为开发者我们知道会有确定结果,所以可以这样写:
const fin = finder as number
const fin2 = <number>finder
需要注意的是,这里并不是类型转换,而仅仅是编译时用的,编译完成后不会再存在。
# 接口 Interface
用来约定对象中成员类型;仅用来做类型约束,不会被编译到生产环境
interface User {
name: string
age: number
addr?: string // 可选
readonly birth: string // 只读属性
}
function updateUser(user: User) {
user.name = 'jason'
user.age = 19
user.birth = '1999/09' // 报错,只读属性
}
interface Cache {
[prop: string]: string // 动态属性,任意类型
}
# 类 Class
类,用来描述一类具体事物的抽象特征;TS中增强了类的语法;
class Animal {
public name: string // 默认public修饰
private age = 18 // 私有属性
protected gender = true //受保护,只允许子类访问
readonly birth: string // 只读属性
constructor(name: string, age: number) {
this.name = name
this.age = age
}
say(msg: string): void {
console.log(msg)
}
}
const cat = new Animal('meme', 19)
console.log(cat.gender) // 报错访问不到,受保护
class Cat extends Animal {
constructor(name: string, age: number) {
super(name, age);
console.log(this.gender) // 可以访问
}
}
// 抽象某种单一能力,更易于组合使用
interface Eat {
eat(msg: string): void
}
interface Run {
go(msg: string): void
}
class Cat implements Eat, Run {
eat() {
console.log('cat eat')
}
go() {
console.log('cat go')
}
}
class Dog implements Eat, Run {
eat() {
console.log('dog eat')
}
go() {
console.log('dog go')
}
}
//抽象类 只能继承,不能实例化
abstract class Animal {
eat(food: string): void {
console.log('eat', food)
}
abstract run(addr: string): void
}
class Dog extends Animal {
run(addr: string): void {
console.log('run', addr)
}
}
const dog = new Dog()
dog.eat('meat')
dog.run('shangshai') // 均可直接使用
# 泛型
定义函数接口或者类时,没有指定具体类型,而是在使用时指定类型。
这样可以最大程度复用代码;
function createNumArr(len: number, val: number): Array<number> {
return Array(len).fill(val);
}
function createStrArr(len: number, val: string): Array<string> {
return Array(len).fill(val);
}
以上两个函数作用非常类似,初始化指定长度的数组,并用给定值填充;但却不得不写两个函数
这就需要泛型出场了。
function createArr<T>(len: number, val: T): Array<T> {
return Array(len).fill(val);
}
// 这样就可以创建任意类型的数组了
createArr(10, 'abc')
createArr(10, true)
createArr(10, 100)