【TS】TypeScript语法学习

1、安装typescript

这一篇幅仅仅涉及到ts的基本语法,没有涉及到ts项目以及配置

typescript全局安装,我这里使用的是cnpm,npm或者yarn同理

cnpm install -g typescript
cnpm install -g ts-node
cnpm install -g tslib @types/node	# 包含console等辅助函数的ts库

安装完毕之后输入tsc -v查看版本,同时检查是否安装成功

ts安装

2、变量

和kotlin一样,使用: (type)来声明变量的类型

2.1 ES6原生类型

如下是ES6中的原生类型

let num: number = 0xdeadbeef
let bol: boolean = true
let str: string = "woodwhale"
let nul: null = null
let und: undefined = undefined
let obj: object = new String("sheepbotany")

2.2 任意类型

任意类型包括两种,一种是any,一种是unknow

其中,any类型可以调用属性和方法,unknow无法调用

let anys: any = "我是任意的,随便赋值"
anys = 114514   // number
anys = false // 布尔
anys = null
anys = undefined
anys = []   // 数组
anys = {
    
    a: 123, b: ():number => 114514}   // 字典
anys.a  // 可以调用属性和方法
anys.b()

let unk: unknown = "未知类型,比any安全"
unk = {
    
    a: 123}
// unk.a  无法调用

再来看第二个区别,any可以当作父类也可以当作子类,unkown只能当作父类、不能当作子类。

如果非要给unknow赋值,可以给其赋值any类型的变量或者unknow类型的变量

let test: any = "test"
let test2: string = "test2"
test2 = test    // 可以
console.log(test2)

let test3: unknown = "test3"
// test2 = test3 不可以
test3 = test    // 可以

2.3 数组类型

两种形式,面向结构和面向对象

let str_arr: string[] = ["1","1","4","5","1","4"]
let num_arr: number[] = [1,1,4,5,1,4]
let any_arr: any[] = [1,"1","4",[5],"1",4]
let bol_arr: boolean[] = [true,false]

let obj_arr: Array<string> = str_arr	// Array接口
console.log(obj_arr)

如果是多维数组,使用下面的形式

let str2_arr: string[][] = [["1"],["2"]]
let obj2_arr: Array<Array<string>> = str2_arr
console.log(obj2_arr)

在ES6中多了一个...的扩展运算符,用于取出参数对象中的所有可便利的属性,可以配合数组使用,这里拿函数举一个例子

function fun_arr(...args: any): void {
    
    
    console.log(args)   // 传入参数的数组
    console.log(arguments)  // 内置的arguments对象
}

fun_arr(1,1,4,5,1,4)
/*
    [ 1, 1, 4, 5, 1, 4 ]
    [Arguments] { '0': 1, '1': 1, '2': 4, '3': 5, '4': 1, '5': 4 }
*/

除此之外,我们可以使用接口来定义自己的数组

interface myNumberArr {
    
    
    [arr_index: number]: number
}

let my_arr: myNumberArr = [1,2,3,4]
console.log(my_arr)

/*
	[1,2,3,4]
*/

2.4 联合类型

在ts中,声明变量的类型可以使用标识符 | 来进行类型的联合,例如我需要一个变量可以置为字符串,同时也可以置为数字,那么可以这样使用

// 联合类型,可以是字符串,也可以是数字
let phone_number: number | string = "1919810"
phone_number = 114514

// 联合类型使用的小例子
let fn = (flag: number | boolean): boolean => {
    
    
    return !!flag
}
console.log(fn(1) === fn(true))

2.5 交叉类型

和联合类型相反,交叉类型必须满足多种类型的需求,必须同时满足

interface People {
    
    	// 人 的接口
    name: string,
    age: number
}
interface Man {
    
    		// 男人 的接口
    sex: string
}
const woodwhale = (man: People & Man): void => {
    
    
    console.log(man)
}
woodwhale({
    
    
    name: "woodwhale",
    age: 19,
    sex: "我太男了"
})
/*
    { name: 'woodwhale', age: 19, sex: '我太男了' }
*/

2.6 类型断言

这里所谓的断言可以理解为强制转换,使用关键字as来进行强转,或者使用<type>来进行强转

let assertion_fun = (num: number | string):void => {
    
    
    console.log((num as string).length) // 将num强转为string类型
    console.log(<number>num)    // num强转为number类型
}
assertion_fun("114514")
/*
    6
    114514
*/

2.7 自动推断

如果我们在ts中没有声明变量的类型,ts会进行自动推断

let _str = "woodwhale"   // 自动推断为string类型
let _num = 114514   // 自动推断为数字类型
let _any    // 自动推断为any类型

2.8 类型别名

在ts中使用type关键字可以进行类型起别名的操作,这里演示联合类型与类型别名的使用

type sn = string | number

let str_or_num: sn = "114514"
str_or_num = 1919810

type还可以定义函数类型

type fun = () => string // 一个 无参数的 返回string类型的 方法

let my_fun: fun = function f() {
    
    
    return "114514"
}
// 或者匿名函数
let my_fun_without_name: fun = () => "1919810"

console.log(my_fun())
/*
	114514
*/

type还可以指定变量的种类,起到限定的作用

type flag = true | false | "true" | "false" | 1 | 0

let flag:flag = true

2.9 元组类型

typescript中有一种类型叫做元组类型,是数组的变种,可以存放不同数据类型的数据

let arr:[string,number] = ["woodwhale",19]

console.log(arr)
/*
	[ 'woodwhale', 19 ]
*/

arr.push("sheepbotany",21)

console.log(arr)
/*
	[ 'woodwhale', 19, 'sheepbotany', 21 ]
*/

如果需要添加数据到元组中,使用push方法就可以,但是必须是申明过的数据类型之一(这里必须是string或number)

2.10 never类型

在ts中,never类型表示不可能达到的一种类型,例如既要满足是string,又要满足是number的交叉类型

type flag = true | false | "true" | "false" | 1 | 0

let flag:flag = true

在这里插入图片描述

或者我们定义一个异常的方法,也可以让方法返回值为never类型

function _error(error_msg:string):never {
    
    
    throw new Error(error_msg)
}

_error("test error")

在这里插入图片描述

但是在实际开发过程中,never类型最重要的是一个检查机制,例如在switch多分支选择的时候,如果甲方需要添加新的需求,那么新程序员在补充shit山代码的时候,可能会没有考虑到switch中的分支导致项目错误,这个使用使用never类型的常量进行default的兜底处理就非常重要了,这样就可以在ts编译的时候就发现错误

interface AAA {
    
    
    type: "AAA"
}

interface BBB {
    
    
    type: "BBB"
}

interface CCC {
    
    
    type: "CCC"
}

type ABC = AAA | BBB |CCC
let switch_type = (val:ABC) => {
    
    
    switch (val.type) {
    
    
        case "AAA":
            break
        case "BBB":
            break
        default:
            const check:never = val
            break
    }
}

在上述代码中,没有考虑到CCC的情况,那么在default中会有一个check的检查报错,可以提醒我们代码有问题!

在这里插入图片描述

正常修改后的代码应该是如下:

let switch_type = (val:ABC) => {
    
    
    switch (val.type) {
    
    
        case "AAA":
            break
        case "BBB":
            break
        case "CCC":
            break
        default:
            const check:never = val
            break
    }
}

2.11 symbol类型

symbol类型是ES6的新特性之一,目的是为了拥有唯一性。

下面列出了几种方法,来测试symbol如何读取

let s: symbol = Symbol("woodwhale")
let s2: symbol = Symbol("woodwhale")

let map = {
    
    
    [s]: "value",
    [s2]: "value2",
    name: "woodwhale",
    sex: 1
}

// foreach 循环,无法读取symbol的键
for (let key in map) {
    
    
    console.log(key)
}
/*
    name
    sex
*/

// 使用Objeck.keys方法获取键,也无法读取symbol的值
console.log(Object.keys(map))
/*
    [ 'name', 'sex' ]
*/

// 使用Object.getOwnPropertyNames方法获取属性名也无法读取symbol
console.log(Object.getOwnPropertyNames(map))
/*
    [ 'name', 'sex' ]
*/

// 使用JSON.stringify,也无法获取symbol
console.log(JSON.stringify(map))
/*
    {"name":"woodwhale","sex":1}
*/

// 使用Object.getOwnPropertySymbols方法只能获取其中的symbol
console.log(Object.getOwnPropertySymbols(map))
/*
    [ Symbol(woodwhale), Symbol(woodwhale) ]
*/

// 使用Reflect.ownKeys可以获取所有的键,包括symbol
console.log(Reflect.ownKeys(map))
/*
    [ 'name', 'sex', Symbol(woodwhale), Symbol(woodwhale) ]
*/

在Symbol对象中有一个iterator的属性,也叫做迭代器,每一个可以遍历的数组都具有这个性质(自己写的类生成的对象是没有的),迭代器具有next()方法,可以迭代到下一个位置

let _arr:Array<number> = [1,1,4]
let __arr:number[] = [1,33,4,5]

let it = _arr[Symbol.iterator]()

console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())

/*
    { value: 1, done: false }
    { value: 1, done: false }
    { value: 4, done: false }
    { value: undefined, done: true }
*/

其中value就是当前迭代的值,done表示是否迭代到头了

根据迭代器的性质,写一个迭代方法

function gen(arr: any) {
    
    
    let it = arr[Symbol.iterator]()
    let flag = false
    while (!flag) {
    
    
        let next = it.next()
        flag = next.done
        if (!flag) console.log(next)
    }
}

gen(new Set([1, 4, 5, 6]))
/*
    { value: 1, done: false }
    { value: 4, done: false }
    { value: 5, done: false }
    { value: 6, done: false }
*/

ts的编写者肯定考虑到了上述场景的使用情况,所以直接用了一个of关键字的语法糖来实现迭代生成

for (let item of new Set([1, 4, 5, 6])) {
    
    
    console.log(item)
}
/*
    1
    4
    5
    6
*/

到这里我们会发现 ofin 有什么区别呢,其实 of 是对容器中的值进行迭代,是一个获取值的方式,而 in 是对容器中的键进行遍历,是一个获取索引的方式

3、函数

ts的函数和js差不多,但是多了很多的拓展,这里列出一些比较常用的属性

  • 默认参数函数

    function fun_test(name: string, age: number): void {
          
          
        console.log(name + age)
    }
    
    // 携带默认参数的方法
    function fun_test_with_default_args(name: string = "woodwhale", age: number = 19): void {
          
          
        console.log(name + age)
    }
    
    fun_test("woodwhale", 18)
    fun_test_with_default_args()
    /*
        woodwhale18
        woodwhale19
    */
    
  • 方法重载

    // 方法重载,重载的方法需要声明,同时满足不同变量(不同的返回值类型)
    function over_fun_test(params: number): void
    function over_fun_test(params1: number, params2: string): void
    function over_fun_test(params: number, params2?:string): void {
          
          
        console.log(params,params2)
    }
    
    over_fun_test(1)
    over_fun_test(2,"重载方法")
    /*
        1 undefined
        2 重载方法 
    */
    

4、接口

在typescript中,规范了类和接口的使用,我们先学习接口的使用

使用关键字interface来定义接口,两个名字相同的接口会进行合并的操作,实现某个接口的变量需要完善其接口中的每个定义,否则报错

interface Person {
    
       // 接口
    name: string
}
interface Person {
    
      // 两个重名的接口是会合并的
    readonly age?: number    // ? 表示 可选操作符, 表示这个属性在定义的时候可有可无,readonly关键字表示属性是只读的,不可以编辑
}
interface Person {
    
    
    [propName: string]: any //  [propName: string]表示可以自定义填充键,使用any表示值的类型可以是随意的
}
interface Person {
    
    
    fun(): string   // 定义函数
}
let a: Person = {
    
    
    name: "woodwhale",
    age: 19,
    test: {
    
    },
    fun: () => {
    
    
        return "sheepbotany"
    },
}
console.log(a)
/*
    { name: 'woodwhale', age: 19, test: {}, fun: [Function: fun] }
*/

使用extends关键字还可以让接口继承

interface PersonPro extends Person {
    
    
    sex: string
}

let b: PersonPro = {
    
    
    name: "sheepbotany",
    age: 21,
    fun() {
    
    
        return "woodwhale"
    },
    sex: "女"
}
console.log(b)
/*
    { name: 'sheepbotany', age: 21, fun: [Function: fun], sex: '女' }
*/

5、类

5.1 简单的例子

在typescript完善了js的面向对象的规范,这里直接举一个最简单的例子,编写一个Person类,具有name、age、sex的属性

class Person {
    
    
    name:string
    age:number
    sex:string
    constructor(name:string,age:number,sex:string) {
    
    
        this.name = name
        this.age = age
        this.sex = sex
    }
}

let woodwhale = new Person("woodwhale",19,"man")
console.log(woodwhale)
/*
	Person { name: 'woodwhale', age: 19, sex: 'man' }
*/

写完感觉非常像kotlin和java,特别是这个构造函数constructor

但是在typescript也有规定,需要声明属性,如果有没有使用到的属性,需要选择赋予初值

5.2 修饰符

对于每个class的属性,都有属性可以用修饰符修饰

  • public 内部外部都可以访问,同时是默认的
  • private 私有的,只能在内部使用
  • protected 保护的,外部不可访问,内部可以使用,子类可以访问

5.3 父子类

使用关键字extendssuper关键字完成父子类的构建,和Java一样,就不多说了,举一个例子就懂了

class Person {
    
    
    name:string
    age:number
    sex:string
    constructor(name:string,age:number,sex:string) {
    
    
        this.name = name
        this.age = age
        this.sex = sex
    }
}

class Man extends Person{
    
    
    constructor(name:string,age:number) {
    
    
        super(name,age,"man")
    }
}

5.4 static静态修饰

这个和Java也是一样的,static可以修饰属性,也可以修饰方法

class Person {
    
    
    name:string
    age:number
    sex:string
    constructor(name:string,age:number,sex:string) {
    
    
        this.name = name
        this.age = age
        this.sex = sex
    }
    static say() {
    
    
        console.log("say something")
    }
}

Person.say()

static修饰的方法是不可以访问类内部的非static的属性

5.5 接口实现

和Java一样,typescript是单继承多实现,只能继承一个类,但是可以实现多个接口,这里举一个简答的例子看看就懂了

interface Person {
    
    
    say(sth:string):void
}

interface Animal {
    
    
    run():void
}

class Man implements Person,Animal {
    
    
    run(): void {
    
    
        console.log("动物都可以奔跑!")
    }
    say(sth: string): void {
    
    
        console.log("say --> " + sth)
    }
}

Man这个类实现了AnimalPerson这两个接口

5.6 抽象类

也是和Java一样,面向对象的本质就是面向接口、面向抽象

abstract class A {
    
    
    name:string
    constructor(name:string) {
    
    
        this.name = name
    }
    abstract setName(name:string):void
    abstract getName():string
}

class B extends A  {
    
    
    setName(name:string): void {
    
    
        this.name = name
    }
    getName(): string {
    
    
        return this.name
    }
}

console.log(new B("woodwhale").getName())
/*
	woodwhale
*/

5.7 枚举类

使用关键字enum来申明枚举类

enum Color {
    
    
    red,
    green,
    blue
}

console.log(Color.red)
/*
	0
*/

枚举默认从0开始,但是可以通过申明的方式

enum Color {
    
    
    red = 1,
    green,
    blue
}

console.log(Color.green)
console.log(Color.red)
/*
	2
*/

当然我们也可以声明字符串枚举

enum Color {
    
    
    red = "red",
    green = "green",
    blue = "blue"
}

console.log(Color.red)

当然还可以自定义枚举,想指定啥就指定啥

enum Color {
    
    
    red = "red",
    green = 1,
    blue = "blue"
}

console.log(Color.red)

一般枚举可以配合const使用,这样typescript编译成JavaScript就直接是赋值常量了,如果不是const那么enum会被编译成一个对象

最后一点,也是ts中枚举类听起来比较新的一点,可以进行反向映射,可以推断出枚举的键名,看个例子就懂了!

需要注意,反向映射的前提是键值对中的值是number类型

enum Types {
    
    
    no,
    yes
}

let y:number = Types.yes
console.log(Types[y])
/*
	yes
*/

但是其实吧,我个人感觉这并不是什么新技术,不过是将枚举的index求出来了,然后反推罢了

6、泛型

泛型的引入,就用如下的例子:

function add1(num1:number,num2:number):Array<number> {
    
    
    return [num1,num2]
}
function add2(str1:string,str2:string):Array<string> {
    
    
    return [str1,str2]
}

上述两个函数实现的功能其实是一样的,只不过传入的参数一个树number,一个是string,有没有可能可以通过一种方式将其写成一种函数,并且可以实现两种数据类型的处理呢?

答案是——使用泛型

function add<T>(p1: T, p2: T): Array<T> {
    
    
    let arr: Array<T> = [p1, p2]
    return arr
}

console.log(add<number>(1, 2))      // 人为指定类型
console.log(add<string>("1", "2"))
console.log(add(3, 4))              // 自动推断类型
console.log(add("3", "4"))

我们可以选择人为指定类型,也可以让ts帮我们自动推断类型

当然,我们也可以传入多个泛型

function add_pro<T, U>(p1: T, p2: U): Array<T | U> {
    
    
    let arr: Array<T | U> = [p1, p2]
    return arr
}

console.log(add_pro(114,"514"))

在之后,就不得不提泛型约束

如果我们想调用一个方法获取某个对象的长度lenght,首先需要约束传入的对象具有length属性

interface L {
    
    
    length: number
}

function getLen<T extends L>(p: T): number {
    
    
    return p.length
}

getLen("114514")
getLen([1, 9, 1, 9, 8, 1, 0])

使用T extends L 这样的接口方式来约束泛型

当然还有一种情况,就是取键值对的情况,这可以使用keyof来约束泛型

function prop<T, U extends keyof T>(obj: T, value: U) {
    
    
    return obj[value]
}

let obj = {
    
    
    a: 1,
    b: 2,
    c: 3
}

console.log(prop(obj,"a"))

使用U extends keyof T的方式,这样就避免了传入非"a",“b”,“c”,这样的键,如果传入就会报错。

当然泛型还可以定义泛型类,如下所示

class MyArray<T> {
    
    
    private arr: Array<T> = []
    add(p: T): void {
    
    
        this.arr.push(p)
    }
    show(): Array<T> {
    
    
        return this.arr
    }
    remove(p: T): void {
    
    
        let index = this.arr.indexOf(p)
        if (index >= 0) {
    
    
            this.arr.splice(index, 1)
        }
    }
}

let my_arr = new MyArray<string>()
my_arr.add("woodwhale")
my_arr.add("sheepbotany")
console.log(my_arr.show())

let my_num_arr = new MyArray<number>()
my_num_arr.add(1)
my_num_arr.add(2)
console.log(my_num_arr.show())

/*
    [ 'woodwhale', 'sheepbotany' ]
    [ 1, 2 ]
*/

猜你喜欢

转载自blog.csdn.net/woodwhale/article/details/125668817