TypeScript从入门到入土

前排提示:由于本文作者的TypeScript是自学的,所以对某些内容的理解可能并不准确,本文的目的是以尽可能通俗易懂的方式对TypeScript的一些基础用法进行说明,请不要过于较真。毕竟真的较起真来就不通俗易懂了,对吧?

考虑到发布平台,本文建立在您已有C/C++语言基础的条件上进行说明,如果没有建议先去了解一下再来阅读。

为什么要学习TypeScript?

又到了每日自(瞎)夸(扯)时间。

首先当然是TypeScript的友好性。

如果您已经掌握了C/C++的基本语法且会一点英语,比如认识function啥的,您将能轻松看懂下面一段TypeScript代码:

1
2
3
4
5
6
7
8
function qpow(a:number,b:number){
let s=1
for(;b;b>>=1){
if(b&1)s*=a
a*=a
}
return s
}

作为解释型语言,它与C++更为相似,相较于Python对OIer应当更为友好。

以及,作为JavaScript的简化版(写法意义上),TypeScript支持很多JavaScript的特性,但写起来十分简单。JavaScript,狗都不写

例如,TypeScript的异步写法是前所未有的简单。

TypeScript语法入门

0.前置芝士

  1. 在TypeScript中,分号可写可不写。

  2. TypeScript的注释表达方式与C++完全一致,即//表达单行注释,/**/表达多行注释。

  3. TypeScript中有一个基本概念:“对象”,可以粗略理解为C++中的一个class。

1.变量定义

1.单变量定义

TypeScript中有以下几种常用变量类型:

类型 标识符
布尔值 boolean
数字 number
字符串 string
任意 any

注意,数字类型包含了所有数字(即整数和实数均可以用number定义)。

具体的定义方式有点像Pascal,比如:

1
var score:number=114514

事实上,如果将var改为let,不会有任何实际影响。(反正我写到现在没发现什么区别?)

至于var或者let的具体意义,事实上是等效于C++中的auto的。比如,上面的一段代码也可以这么写:

1
var score=114514

不会有任何问题。

对于any类型,就是类似于Python的变量类型,可以中途更改其类型。

例如,你可以干这种事:

1
2
var score:any='114514'
score=1

不会有任何问题。

2.常量定义

如果想定义一个常量,与C++类似,直接将varlet换成const即可。

如:

1
const now=1919810

3.数组定义

这里有两种方法。一种是利用Array关键字构造:

1
let a:Array<number>=[1,2,3]

当然也可以利用自动识别的特性直接写:

1
let a=[1,2,3]

4.元组定义

这里的元组与C++中同样类似。

1
let tup:[number,string]=[114514,"jianan"]

注意,如果这里直接写成let tup=[114514,"jianan"],会被等效成let tup:any=[114514,"jianan"]。所以如果真的需要一个绑定式的元组,建议写成完整形式。

Tips:在Typescript中,表示字符串可以用’’,``或者””,具体区别会在下文详细讨论。

2.基础运算

与C++完全一致。甚至连运算符都是一样的。

3.字符串运算

这里之所以把字符串单独拎出来细说,是因为TypeScript的字符串操作真的太方便了。

除了基础的用+将两个字符串拼在一起外,还可以直接把字符串和数字拼在一起:

1
2
let a='a'+1
//a->'a1'

那如果是想把数字转成对应的字符再拼起来呢?

这需要用到String对象中的fromCharCode函数:

1
2
let a='a'+String.fromCharCode(65)
//a->'aA'

至于单引号与双引号的区别,这完全是个人习惯,基本上是可以混用的。而单引号与双引号均可表示字符串的好处就是,在字符串内容中含有双引号的时候,用单引号括起来就不需要加转义符;同样,含有单引号的时候,用双引号括起来也不需要转义符。当然如果你非要用双引号括含有双引号的字符串然后加转义符,我也拦不住你对吧,只不过你的代码会很难看而已。

有没有什么简单的方法呢?

有。这就要请出我们的万能字符串标识符:`

如果你用单引号或者双引号表达一个多行的字符串,你将不得不这么写:

1
'Hello,\n    everyone.\n'

但是如果换成`呢?

1
2
`Hello,\n
everyone.\n`

是的,`所括起的字符串不会因为换行被截断。这在在TypeScript嵌入HTML的情况中非常好用。在两个``之间,你可以按正常的方式直接书写HTML,而不用实时考虑缩进之类的问题。

而`一个更强大的功能就是直接插入式的字符串编辑:

比如,现在你有一个变量score,你想将他放在一个句子中输出,如果用单引号,你只能这么写:

1
console.log('Yes your score is '+score)

但是如果用`呢?

1
console.log(`Yes your score is ${score}`)

在书写一个较为复杂的输出格式的时候,使用`和${}添加变量会简单许多。

在尝试将字符串转为数字时,同样有几种做法:

1
2
3
4
var a=parseInt('1234')//a->1234
var b=parseFloat('1.0')//b->1.0
var c=+"114514"//c->114514
var d=Number('1919810')//d->1919810

如果转换的内容不为十进制数字,则以上所有方式均会返回一个NaN对象。

2.条件语句

与C++完全一致。值得一提的是,在TypeScript中,你可以使用===!==来代替==!=,没有什么区别。

3.循环

与C++类似,TypeScript支持whilefor两种循环,写法与C++类似。

比如,你可以这么写:

1
2
let a=[1,2,3]
for(let i in a)console.log(i)

4.函数定义

1.function式定义

与JavaScript的写法基本一致,即一个函数可以套用这样的模板:

1
2
3
function name(params:type) {

}

2.const式定义

这种写法其实有些类似于C++里的#define。

而在TypeScript中,=>标识符可以表示对函数的定义,具体的,=>前的内容为输入的变量,=>后的内容为函数内容。需要注意的是,如果输入的变量不止一个,则需要用()括起来。

比如:

1
const Out=(keyword:string,score:number)=>`The key is ${keyword} and your score is ${score}.`

5.异步编程

1.什么是异步编程?

当一个异步过程调用发出后,调用者不能立刻得到结果。基于事件机制,实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。比如,你现在要有一批数据要大数据要入库,你又不想一边入库一边等待返回结果,你可以用异步,将大数据推入一个队列,然后另外一个线程来操作这个队列里面的数据入库,入完了,就通知一下主线程。这段时间你的主线程可以做任何事。

——百度百科

学废了?

换个通俗易懂的说法:

想象一下你是网上阅卷的系统。你把第16题分配给甲改了之后,显然并不想等甲改完了再去讨论第17题分给谁。于是你把16题丢给了甲,然后没等甲改完就把17题丢给了乙。甲:囸,我不想改16题啊最终甲改完了16题,告诉你得分;乙也改完了17题,告诉了你得分。

这就是异步的处理模式。如果用同步模式处理,你将不得不等甲改完了16题才能让乙去改17题。

2.异步编程有什么作用?

如大家在刚才的例子中所看到的,异步编程能以单线程的形式,实现类似多线程的效果。另外,在某些场合中(比如刚才的改卷),有些函数在调用后并不能立刻给出结果(或者是结果根本无关紧要),这时候为了避免等待函数给出结果的时间里主程序无事可做的情况,我们选择了异步编程。

3.异步编程怎么写?

大多数编程语言的默认编程模式都是同步的。但是在你想要写成异步的模式的时候,如果你使用C++或者Python,你将不得不写成多线程的模式,而TypeScript呢?

直接在函数前加上一个async标识符即可。

还是那个快速幂:

1
2
3
4
5
6
7
8
9
async function ksm(a:number,b:number) {
let s=1
while(b){
if(b&1)s*=a
a*=a
b>>=1
}
return s
}

就行了。

但如果想弄懂TypeScript的异步编程到底是怎么实现的,我们需要先引入两个概念:Promise对象callback回调函数

顾名思义,Promise对象就是一个“承诺”,也就是异步编程中的核心:你调用了我,我承诺会给你一个返回值,但不是现在。正因为此,所以Promise对象默认类型是any,即不确定的。如果要想让它确定,就必须在函数中指定他的类型。

而callback回调函数则是保证你没有写了一句废话的函数。由于异步编程的特性,主进程并不会等它调用的函数给出返回值;于是它调用的函数就必须在运行完毕后主动回来找主进程,从而把返回值交给主进程。

而回调函数就完成了这个任务。

因此,如果想让我们的快速幂不是写了一个废话,从更为规范的角度,它应该写成这样:

1
2
3
4
5
6
7
8
9
async function ksm(a:number,b:number):Promise<number>{
let s=1
while(b){
if(b&1)s=a
a*=a
b>>=1
}
return s
}

然后我们调用它时,需要用.then标识符来指定获得了函数返回结果的时候做什么事:

1
2
3
ksm(2,10).then(res=>{
console.log(res)
})

.then引导的回调函数往往会用const式进行定义,具体格式就是函数返回的所有值会按顺序分配给.then里变量区域的各个变量,后续函数内容应当对变量区域新定义的变量操作。

如果是函数出错了怎么办?

这在工程开发中相当常见。一般出现错误的时候,我们会使用throw语句跑出错误:

1
throw(err)

而回调函数同样也具有解决报错的功能。

我们使用.catch标识符表达获得意外返回值的时候做什么:

1
2
3
4
5
ksm(2,10).then(res=>{
console.log(res)
}).catch(err=>{
console.error(err)
})

如果不写.catch回调函数,则当ksm函数出错时,程序将因为不知道应该干什么而报错。

6.TypeScript中的IO

与JavaScript类似,我们用console对象来进行标准的输入输出。

console.log和Python中的print完全类似。

1
2
console.log('a','b','c')//输出a b c
console.log('%d + %d = %d',1,1,2)//输出1 + 1 = 2

console.infoconsole.warnconsole.errorconsole.log的区别就在于他们都会在输出的行首加上一个特殊的符号。具体如下表:

函数 符号
console.info 蓝色i标识
console.warning 黄色三角形标识
console.error 红色叉标识

而文件IO需要用到fs库,这里就不展开说了。

用TypeScript写个脚本

用TypeScript写个脚本有什么好处?

当然是方便。

得益于TypeScript极为方便的异步编程,我们可以轻松的用单线程实现这样的效果(50cookies,30s cd):