Typescript

从本文你能了解到那些知识

  • 强类型和弱类型
  • 静态类型和动态类型
  • JavaScript自有类型系统的问题
  • Flow静态类型检查方案
  • TypeScript 语言规范和基本使用

强类型与弱类型 (类型安全)

TypeScript 是一门基于 JavaScript 之上的编程语言,它重点解决了 JavaScript 语言本身类型系统不足的问题。

强类型:具有类型约束 不允许隐式类型转换 约束后,不符合类型的编译阶段就会报错,而不是执行阶段

弱类型:不具有类型约束 允许隐式类型转换

静态类型和动态类型

静态类型 一个变量声明时他的类型就是明确的,并且不可修改

动态类型 运行阶段才可以明确变量类型,而且变量的类型随时可以改变

js 弱类型产生的问题

const obj ={}
obj.foo()  //只有运行阶段才发现错误

//如果错误放异步中,就会存有隐患
setTimeout(() => {
  obj.foo()
}, 1000000);
//类型不明确导致无法确定函数功能
function sum (a,b){
  return a+b
}

console.log(sum(1,2));
console.log(sum("100",100));

相比于弱类型,强类型的优势

  • 错误更早的暴露
  • 代码更智能,编码更准确 根据强类型的智能提示
  • 重构更牢靠
  • 减少不必要的判断

Flow JS类型检查器

2014 facebook 类型注解可控不是都必须加

  • flow安装

    npm install flow-bin -D
    npx flow init  //生成.flowconfig

    使用flow时关闭vscode的语法校验,因为添加的类型注解不是标准的js语法

    文件 -> 首选项 -> 设置 -> javascript validate

    添加注释@flow,标记进行flow检查的文件

控制台使用 yarn flow 会查找node_modules里的.bin/flow进行类型检查

//@flow

function sum(n:number,m:number){
  return n+m
}

// console.log(sum("100",100)) //"100"会报错
console.log(sum(100,200))

yarn flow stop 结束服务

Flow 移除注解

@flow并不是js的语法。因此执行 node +文件.js 或浏览器中 会在控制台报错

编码过后自动移除掉类型注解

  • 方式1 flow官方移除

    • 安装:yarn add flow-remove-types --dev
    • 使用:yarn flow-remove-types . -d dist 点(改成src)是所有文件 后面是输出位置,作用是去除类型注解,在node和浏览器中使用
    • 会在dist生成对应js文件
  • 方式2 babel插件

    • 安装:

      npm i @babel/core @babel/cli @babel/preset-flow -D

      yarn add @babel/core @babel/cli @babel/preset-flow --save

    • 使用:yarn babel src/part2_typescript/ts_typescript_06.js -d dist/js
    • yarn babel src -d dist 编译src文件用babelflow

Flow vscode插件

  • Flow Language Support,好处是在编译阶段就报错上面都是运行后控制台报错

Flow 类型推断

  • 建议还是添加类型注解

    function square(n){
    return n * n
    } 
    // square("100") //n报错
    
    let num:number = 1
    // num = "streing"

    Flow类型注解类型

  • string number boolean null undefined symbol
  • array object function
const a:string = "1"
const b:number = NaN//100, Infinity
const c:boolean = true
const d:null = null
const e:void = undefined
const f:symbol = Symbol()
const arr:Array<any> = [1,true]
const arr1:any[] = [1,true]
const fw:[string,number] = ['foow',1] //固定长度类型的数组成为元组
const obj_:{foo?:string,bar:number} = {foo:"1as",bar:1} //可以可选

const obj:{[string]:string} = {} //通过key给空对象添加,约定key和value
ob.key1 = "value"

function fn(callback:(string,number)=>void){ //函数参数指定函数
  callback("str",1)
}
fn(function(str,n){
})

特殊类型

//字面量 
const wc:"foo" = "foo"
//字面量配合联合类型
const type:'success' | "warning" | "danger" = "success"
const nb: string | number = 1
//类型别名
type StringOrNumber = string | number
const sb:StringOrNumber = "stri"
//MayBe类型,在具体类型基础上扩展了null和undefined两个值,添加?

const gender:?number = null
const gender1:number | null | void = undefined //同上

//Mixed Any 任意类型 所有类型的联合类型 any是弱类型,mixed是强类型
//string | number | boolean

function passMix(value:mixed){
  // value * value  报错
  // value.split(",") 报错

  if(typeof value === 'string'){ //这个联合类型需要判断
    value.split(",")
  }
  if(typeof value === 'number'){
    value * value
  }
}


function passAny(value:any){
  value * value
  value.split(",")
}

typescript

ts官网

  • 安装

    npm i typscript -D
    npm i ts-node -D
    tsc --init

    tsc xxx

    ts-node xxx

相比较flow 生态健全强大,完善 且渐进式的,什么不会也完全当作js用

缺点是多了很多概念,接口泛型枚举

不同文件同名冲突的问题。js不会报错会覆盖,但是ts会在显示阶段就提醒你报错,
因此,通过IIFE或者 export{}形式将文件形成私有作用域

//单独作用域
// (function(){

  const hello = (name:string) =>console.log(`Hello,${name}`);
  hello("TypeScript")
  //原始类型 

  //string,number,boolean都可以设置成null or undefined
  const a:string = "aa"
  const b:number=1//NaN  Infinity
  const c:boolean= false
  const d:string = null

  const e:void = undefined //null
  const f:null = null
  const g:undefined = undefined
  const h:symbol = Symbol()  

//})()


//改成模块,所有成员都是局部成员,不是导出了个对象
export {}

首选项 typescript local -> zh-CN修改成中文错误提示

object类型

//Object类型不单指对象,而是原始类型以外的其他类型,

//`number,string,boolean,symbol,null或undefined`之外的类型

const foo:object = function (){}//{},[]
const obj:{foo:number,bar:string} = {foo:123,bar:"as"}//这种形式可以用interface搞定
declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

array类型

const arr:Array<number> = [1,2,3]
const arr2:number[] = [1,2,3]

function sum(...args:number[]){
  return args.reduce((executor,current)=>{
    return executor+current
  })
}
console.log(sum(1,2,3))

元组

//明确元素数量和元素类型的数组
const tuple:[number,string] = [1,"1"]
tuple[0]  
const [age,name] = tuple

枚举

const status = {
  Before:0,
  Doing:1,
  After:2
}

//默认从0开始累加,枚举会影响我们编译后的结果(看下编译的js文件), 
enum Status{
  Before,
  Doing,
  After
  // After = "aff" //字符串枚举,不常见
}

// const enum Status{ //加const为常量枚举
//   Before,
//   Doing,
//   After
// }

const s = {
  current:Status.Doing
}
console.log(s);

//有const 不能通过索引访问枚举名
console.log(Status[0])

函数类型

//函数类型
function sth(a:number,b?:number,c:number=10):string {
  return ""
}
function sth1(a:number,...args:number[]):string {
  return ""
}

const sth2:(a:number,b:number)=>string = function (a:number,b:number):string {
  return ""
}

任意类型

//any不会做类型检查,语法上不会报错
function sth3(s:any){

}

隐式类型推断

//(建议添加明确类型)
let num = 18
// num = "" //类型错误
let fx   //any类型

类型断言

//并不是类型转换,代码编译过后不存在了就
const nums = 1;
const mn = nums as number
const mn2 = <number>nums  //jsx 不支持

接口 interface

约定一个对象有哪些成员,这些成员类型是什么

接口就是用来约束对象的结构,一个对象实现一个接口他就必须拥有接口所约束的成员

接口只是类型约束,运行阶段就没了

interface Person{
  age:number //逗号,分号,不加
  name:string
  sex?:string
  readonly height:number
  [key:string]:any
}

function student(s:Person) {
  console.log(s.age);
  console.log(s.name);
}

const stud:Person = {
  age:1,
  name:"as",
  height:199
}
// stud.height = 180

stud.va = "asd"
stud.as = 112

函数类型接口

interface SearchFunc {
  (source: string, subString: string): boolean;
}
let mySearch = <SearchFunc>function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
}

类类型接口

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

类静态部分与实例部分的区别

当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:

interface ClockConstructor {
    new (hour: number, minute: number);
}

class Clock implements ClockConstructor {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内

因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数 createClock,它用传入的类型创建实例。

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}

let digital = createClock(DigitalClock, 12, 17);

接口继承

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

class类

描述一类具体事务的抽象特征 (手机)

声明,修饰符,构造函数,静态方法


class Person{
  name:string //要声明相比js(es7)
  private age:number //默认public
  protected gender:boolean
  readonly sex:string = "as" //只读

  constructor(name:string,age:number){
    this.name = name
    this.age = age
  }
  say(msg:string):void{
    console.log(`hi ${this.name},${msg}`);
  }
}
const mc = new Person("mcgee",18)
mc.name
// mc.age
// mc.gender
class Student extends Person{
  constructor(name:string,age:number){
    super(name,age)
    console.log(this.gender); //可访问
    
  }
}
class Student2 extends Person{
  private constructor(name:string,age:number){ //构造函数添加private  则无法实例化,无法被继承
    super(name,age)
    console.log(this.gender);
  }
  static create(name:string,age:number){
    return new Student2(name,age)
  }
}

// new Student2("MC",16) //错
Student2.create("MC",16) //可以

//给构造函数添加protected 无法被实例化,但是可以被继承

类与接口

类与类之间有公共特征,通过接口抽象 (手机和座机都能打电话)
interface Eat{
  eat (food:string):void
}

interface Run{
  run (distance:number):void
}


class Person implements Eat,Run{ //类实现了接口必须有接口的成员
  eat(food:string){

  }
  run(distance:number){

  }
}

// class Animal implements EatAndRun{
//   eat(food:string){

//   }
//   run(distance:number){

//   }
// }

抽象类

类似于接口,约束子类中必须有某个成员,区别是抽象类可以包含一些具体的实现,接口只能是成员的抽象不包含具体实现

抽象类只能被继承,不能new

abstract class Animal{
  eat(food:string):void{
    console.log(food);
  }

  abstract run(distance:number):void //抽象方法不需要方法体
}

class Dog extends Animal{
  run(distance:number):void{

  }
}

const d = new Dog()
d.eat("sss")
d.run(100)

泛型

定义函数,接口或类的时候没有指定具体类型,等到我们使用的时候再去指定具体类型

目的是极大程度的复用代码

function  arraynumber(length:number,value:number) :number[]{
  return Array<number>(length).fill(value)
}

const res = arraynumber(3,100) //[100,100,100]

//如果再定一个arraystring
function  arraystring(length:number,value:string) :string[]{
  return Array<string>(length).fill(value)
}

//把定义时不能明确的类型变成参数,使用时传递
function createArray<T>(length:number,value:T):T[]{
  return Array<T>(length).fill(value)
}

createArray<string>(4,"mcgee")

泛型类型

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <U>(arg: U) => U = identity;
//泛型类型接口
interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;
//绑定了接口泛型类型
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

泛型类

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型约束

//泛型T满足接口length属性
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

类型声明

引入第三方模块时候,如果第三方模块不包含类型声明,可以自声明

引入 query-string

import {camelCase} from 'lodash' //@types/lodash
import qs from 'query-string'

declare function camelCase (input:string):string //添加后res:string

const res = camelCase('hello camelCase') //res:any

qs.parse("?key=asdasdq122") //自带类型约束

mcgee0731
60 声望4 粉丝

不会做饭的程序猿不是一个好厨子


下一篇 »
JS性能优化1