js语言特性与typescript详述
强类型与弱类型(类型安全)/静态类型与动态类型(类型检查)
强类型与弱类型用来区分编程语言的类型安全。强类型是在语言(语法)层面限制函数的实参类型必须与形参类型相同。弱类型不会限制实参的类型。(强类型语言中不允许有任何隐式类型转换,弱类型语言允许任何隐式转换)。
静态类型语言:一个变量在声明的时候,它的类型就是明确的,声明过后类型不允许被修改。
动态类型语言:在运行阶段才能明确变量类型,变量类型可以随时发生变化。在动态类型语言中,变量是没有类型的,而变量存放的值是有类型的。
为什么js没有被设计成强类型+静态类型的语言?
早期js应用简单,js是一个脚本语言,不需要进行编译就可以在运行环境中运行,设计者并没有预见到现在的情形。
弱类型的问题
const obj={
}
obj.foo() // TypeError: obj.foo is not a function
写的时候没有问题,语法正确,但是在运行时就会报错。如果这段代码时异步的,在测试阶段没有检测出来,就会给系统带来隐患。
而如果是强类型的语言,在语法上就会直接报错,不会等到运行阶段。
function sum(a,b){
return a+b
}
console.log(sum(1,'100')) // 1100
如上,如果两个参数传入的非约定值,就会产生意料之外的结果,函数功能发生改变。
强类型的优势
错误会更早的暴露,不用最后在运行时才会表现;
代码更加智能,编码更加准确;(编辑器中的智能提示)比如,写一个render函数,传入dom元素,此时编辑器不会弹出dom元素的属性值,因为编辑器并不知道:
function render(element){
element.className='container'
element.inerHtnl='xxxx' // 这里属性名拼错了,但不会有提示
}
重构更加牢靠,比如:在重构时发现属性名不规范,想要重改,就需要很谨慎的处理,一旦有错误,就会造成系统的崩溃。而如果是强类型语言的话,在编译阶段就会报出错误,就会定位到所有使用到这个成员的地方。
const obj={
aaa:()=>{} //命名不规范
}
强类型语言会减少不必要的类型判断。
Flow
flow是js的类型检查器,会为函数添加类型注解
function sum(a:number,b:number){
return a+b
}
安装与配置
yarn add flow --dev
yarn flow init // 初始化
yarn add flow-remove-types --dev
yarn flow-remove-types [输入目录] -d [输出目录] // 删除代码中的flow检查输出到输出目录
使用上述命令,就能把有flow检查的文件转化为没有flow检查的文件,比如:
// @flow
function sum(a:number,b:number){
return a+b
}
console.log(sum('100',1))
转化后
function sum(a ,b ){
return a+b
}
console.log(sum('100',1))
或者使用babel来进行上述转化:
yarn add @babel/core // babel核心文件
yarn add @babel/cli // 直接在命令行使用bebel
yarn add @babel/preset-flow // 包含转换flow注解的插件
安装过后在项目根目录新建配置文件.babelrc
{
"presets":["@babel/preset-flow"]
}
之后,在命令行运行
yarn babel [输入] -d [输出]
同样可以得到去flow注解后的代码:
function sum(a, b) {
return a + b;
}
console.log(sum('100', 1));
flow开发工具插件
如果用上述手动敲命令行并不方便,我们可以安装开发工具插件,在vscode中安装扩展flow language support这个插件
flow中数组类型
// @flow
const arr1:Array<number> =[1] // 全部由数字组成的数组
const arr2:number[]=[1,2,3]
// 固定长度数组-元组
const arr3:[string,number]=['1',2] //
//const arr3:[string,number]=[1,'2'] // flow报错
对象类型
// @flow
/* const obj1:{foo:string,bar:number}={
foo:'a',
} */ // 不写全属性flow会报错
const obj1:{foo:string,bar:number}={
foo:'a',
bar:1
}
// 对象中固定值的类型
const obj2:{[string]:number}={
foo:1,
// bar:'dddd' // 报错
}
flow函数类型
// @flow
function foo(callback:(string,number)=>void){
callback('a',100)
}
foo(function(a,b){console.log(`${a} is ${b}`)})
特殊类型
// @flow
// 字面量类型
const a:'foo'='foo'
// const b:'foo'='foo1' // 报错,只能传'foo'
// 联合类型
const type:'success'|'warning'|'error'='error'
const b:string|number='aaa'
// maybe类型
const gender:?number=undefined
const gender1:?number=null
const gender2:?number=1
任意类型
// @flow
// mixed类型 强类型 所有类型
// string|boolean|number|...
function passMixed(value:mixed){
value.substr(1) // 报错
value*value
}
// any类型 弱类型 尽量不要用any类型
// string|boolean|number|...
function passAny(value:any){
value.substr(1) // 不会报错
value*value
}
TypeScript
ts是js的超集(扩展集),在js的原有基础上多了很多特性,也有更强大的类型系统来完成开发工作,开发过后将编译成js代码。
配置
yarn add typescript
yarn tsc --init // 初始化配置文件
标准库
进入到tsconfig.json中,target为标准库的语言,lib为补充的标准库。
显示中文语言:(不推荐,难以搜索错误)
tsc --locale zh-CN
Object类型
Object类型并不单指对象,还有函数和数组。对象更倾向于用接口类型去写。
元组类型(Turple)
明确元素数量以及以及每个元素类型的数组
const turple:[number,string]=[11,'sdsd',2] // 超出数量报错
const turple2:[number,string]=['22','sdsd'] // 元素类型错误也报错
const turple3:[number,string]=[22,'sdsd']
const [age,name]=turple3
export {}
枚举类型
enum postStatus{
draft=0,
unpublish=1,
published=2
}
// 使用
const post={
id:'dsu2',
status:postStatus.draft
}
常量枚举,如果用上述写法定义枚举类型,每个枚举类型可以通过下标拿到,比如:postStatus[0]
就是draft。使用常量枚举,让下标不能拿到枚举值:
const enum postStatus{
draft=0,
unpublish=1,
published=2
}
postStatus[0] // 报错
隐式类型断言
let age=10
age='xx' // 报错:已经默认把age推断为number类型
let foo; // any:之后可以当作动态类型
foo=1
foo='sss'
类型断言
const nums=[10,20,30]
const res=nums.find(num=>num<20)
const square=res*res // ts不能直接判断出res是一个number类型
// 此时就要断言告诉ts res就是number类型的
// 使用as
const num1=res as number
const square1=num1*num1
// 使用<Type> 不推荐,因为在jsx会产生冲突
const num2= <number>res
const square2=num2*num2
接口
可以抽象理解为规范或是“契约”,它只是用来约定对象的结构,在实际运行阶段并没有意义。
interface Post{
title:string
subtitle?:string // 可选成员
id:number
readonly summary:string // 只读成员
}
const hello:Post={
title:'hello',
id:1,
summary:'sdsdsad'
}
hello.summary='sss' // 报错,只读成员在声明后不能被修改
interface Cache{
[key:string]:string // 动态成员
}
const cache:Cache={
name:'sdsd'
}
cache.foo='bar' // 可以添加任何成员,但必须以string作为键值
类
类用来描述一类具体事物的抽象特征。ts增强了class的相关语法。
在ts中要明确声明类所拥有的属性,而不是通过动态添加。
class Person{
constructor(name:string,age:number){
this.name=name // 报错
this.age=age
}
}
在constructor前要声明类中的成员类型,并要在构造函数里赋予初始值
class Person{
name:string
age:number
constructor(name:string,age:number){
this.name=name // 通过
this.age // 报错,必须有初始值
}
}
类的访问修饰符
class Person{
name:string // 默认就是public
private age:number
protected gender:boolean
constructor(name:string,age:number){
this.name=name // 通过
this.age=age
this.gender=true
}
sayHi(msg:string){
console.log(msg+this.age)
}
}
const tom=new Person('tom',14)
console.log(tom.name)
console.log(tom.age) // 报错!私有成员只能通过成员函数访问
console.log(tom.gender) // 报错!保护成员只能在类和其子类中访问
class Student extends Person{
constructor(name:string,age:number){
super(name,age)
console.log(this.gender) // 可以访问
}
}
constructor默认为public,如果是private的,就要使用静态函数来创建该类的实例。如:
class Student extends Person{
private constructor(name:string,age:number){
super(name,age)
console.log(this.gender) // 可以访问
}
static create(name:string,age:number){
return new Student(name,age)
}
}
const lili=new Student('lili',10) // 报错,类“Student”的构造函数是私有的,仅可在类声明中访问
const lili2=Student.create('lili2',10) // 通过
类与接口
interface EatNRun{
eat(food:string):void
run(distance:number):void
}
interface Sleep{
sleep(hours:number):void
}
class Person implements EatNRun,Sleep{ // 多个接口的实现
eat(food:string):void{
console.log(food)
}
run(distance:number):void{
console.log(distance)
}
sleep(hours:number):void{
}
}
class Animal implements EatNRun{
eat(food:string):void{
console.log(food)
}
// 没有run成员就会报错
}
抽象类
抽象类和接口类似,用来约束子类当中必须要有某类成员,抽象类包含具体的实现,而接口只是成员的抽象,不包括具体的实现。大的类目最好使用抽象类。在抽象类中可以定义抽象方法,抽象方法不需要方法体。
abstract class Animal{
eat(food:string):void{
console.log(food)
}
abstract run(distance:number):void
}
// 非抽象类“Dog”不会实现继承自“Animal”类的抽象成员“run”
class Dog extends Animal{
run(distance: number): void {
console.log(distance) // vscode的快速修复功能,可以直接实现
}
}
泛型
在定义函数、接口、类中没有指定具体的类型,等到使用的时候再去指定的这种特征,其目的是极大程度的复用代码。泛型参数用<T>
表示:
// 同样的逻辑只不过是不同的类型,会造成代码冗余
function createNumberArray(length:number,value:number){
const arr:Array<number> = Array(length).fill(value)
return arr
}
function createStringArray(length:number,value:string){
const arr:Array<string> = Array(length).fill(value)
return arr
}
// 以上代码可以简化为
function createArray<T>(length:number,value:T){
const arr:Array<T> = Array(length).fill(value)
return arr
}
// 使用
const res=createArray<string>(3,'a')
类型声明
import {camelCase} from 'lodash' // lodash文件没有声明camelCase的类型
declare function camelCase(params:string):string
const res=camelCase('hello world')