当前位置: 首页 > 编程日记 > 正文

TypeScript 从听说到入门(上篇)

我为什么会这样念念又不忘 / 你用什么牌的箭刺穿我心脏

我也久经沙场 / 戎马生涯 / 依然 / 被一箭刺伤

——李荣浩《念念又不忘》

接下来我会分上、下两篇文章介绍 TypeScript。

我也是 TypeScript 初学者,这两篇文章是我的学习笔记,来源于一个系列的免费视频,视频不错,如果你觉得看视频学习更快,可以点击这里看到。

TypeScript 与 JavaScript 有何区别呢?简单点说,就是前者引入了类型约束,能实现代码的静态检查,而且能提供更加完善的代码提示功能;除此之外,还引入了接口、抽象类、枚举、访问控制修饰符、泛型等语法,让我们写出的代码更具健壮性和可扩展性。

上、下两篇的文章内容安排如下:

  • 《上篇》
    1. 基本类型
    2. 对象类型
    3. 扩展类型
  • 《下篇》
    1. 面向对象编程
    2. 泛型

《上篇》定义为基础篇,《下篇》定义为深入篇。

我们先从基础篇开始学习。

开发环境

TypeScript 脚本以 .ts 后缀结尾。我使用 VSCode 学习 TypeScript,写好 TypeScript 代码后, 需要将其编译为 JavaScript 代码才能运行。在此之前,我们先要安装 Node.js 环境。

然后安装 TypeScript 编译环境。

$ npm install -g typescript
$ tsc --version
复制代码

编译文件的指令如下:

// 执行完下列语句后,会在同级目录下看到一个 `HelloWorld.js` 文件,
// 就是编译完成后的文件啦
$ tsc HelloWorld.ts
复制代码

codepen.io 中也支持 TypeScript 的书写。新建一个新的 Pen 后,将 JavaScript 一栏的预处理器设置成 TypeScript 即可,你也可以实时查看到编译之后的代码,不过代码提示效果不是很强。

基本类型

我们先从最简单的基本数据类型(Primitive)说起。

ECMAScript 提供了六种基本数据类型:布尔、数值、字符串、Null、Undefined 和 Symbol。

而 TypeScript 针对上述的每一种类型,都提供了对应的类型字面值:boolean、number、string、null、undefiend 和 symbol(ES6 中引入,本系列两篇不对 symbol 做介绍)。

在 TypeScript 中,使用 : type 语法为变量指定类型:

// 声明一个布尔值类型变量 `isMale`,初始值为 `true`
let isMale: boolean = true;
// 声明一个字符串类型变量 `myName`,初始值为 `'Alex'`
let myName: string = 'Alex';
// 声明一个数值类型变量 `myAge`,初始值为 `20`
let myAge: number = 20;
// 声明一个 Null 类型变量 `myGirlFriend`,值为 `null`
let myGirlFriend: null = null;
// 声明一个 Undefined 类型变量 `myHouse`,值为 `undefined`
let myHouse: undefined = undefined;
复制代码

注意:TypeScript 提供的类型字面值都是小写形式,注意与首字母大写的形式区分,后者是 JavaScript 原生提供的构造器函数。

有时,一个变量的类型并不局限于一种。比如,一个变量的值可以是字符串,也可以是数值。这时就要用到“联合类型”了。

联合类型使用竖线 | 分隔,表示某个变量可以给予其中任意一种类型值。

下例中,声明了一个变量 foo,它的值可以是一个字符串,也可以是一个数值。

// 此处声明了一个变量 `foo`,可以是字符串,也可以是数值
let foo: string | number = 'bar'; // 初始值给了字符串 `'bar'`
foo = 123; // 接下来将 `foo` 重新赋值为 `123`
复制代码

对象类型

除了基本类型,工作中最常处理就是对象了。那么如何在 TypeScript 中指定对象类型呢?

定义对象

在 TypeScript 中,使用接口,也就是关键字 interface 来描述对象的形状,也就是对象的类型。“接口”在传统的面向对象编程的语言里,比如 Java,表示“行为的抽象”,而在 TypeScript 对此稍有不同,接口不仅可以表示行为的抽象,还可以用来定义对象类型。

接下来,我们定义一个类型 Person(按照约定,首字母大写):

// 用接口声明一个类型 `Person`
interface Person {name: string;age: number;
}// 将变量 `alex` 声明为 `Person` 类型
let alex: Person = {name: 'Alex',age: 20
};
复制代码

注意:定义接口时,属性之间可以用分号 ;、也可以用逗号 , 分隔,甚至什么都不加也可以。

我们定义了一个类型 Person,并将变量 alex 的类型声明为 Person。那么,在给 alex 赋值时,必须严格符合类型定义:赋值对象必须由一个字符串属性 name和一个数值属性 age 组成,缺少或多出的属性,都会提示错误。

// 会提示出错(缺少一个属性)
let alex: Person = {name: 'Alex';
};// 会提示错误(多了一个属性)
let alex: Person = {name: 'Alex',age: 20,gender: 'male'
};
复制代码

定义类型时,如果想要表示某个属性是可选的,则使用 ?: type 语法声明。

// 类型 `Person` 的 `name` 属性是可选的 
interface Person {name: string;age?: number;
}// 因为 `age` 是可选属性,所以赋值时不给也行
let alex: Person = {name: 'Alex'
};
复制代码

除了定义可选属性,还可以定义“任意属性”。

所谓的任意属性,就是我们不确定将来会添加的属性名称是什么,但是会提前定义允许添加的属性,在不确定未来这个属性名的情况下,限制这个属性的类型。

// Person 中定义了一个任意属性,属性类型是 `any`
interface Person {name: string;age?: number;[propName: string]: any;
}// 我们给变量 `alex` 添加了一个任意属性 `gender`
let alex: Person = {name: 'Alex',gender: 'male
};
复制代码

我们使用 [propName: string]: any 的形式,定义了一个 any 类型的任意属性。

注意,任意属性的类型,必须是上面的已知属性 nameage 类型的超集,否则会提示出错。 比如,上面我们可以将上面任意属性的类型 any 修改为 string | number 也是可以的。

说完对象,再来介绍数组。

数组类型

在数组上指定类型,本质上是限制数组成员的类型。在 TypeScript 中,使用 type[] 语法指定数组成员的类型。

下面定义了一个数组,限制其成员只能是字符串。

// 此处定义了一个数组 `myFriends`,其成员限定为只能是字符串
let myFriends: string[] = ['Alex', 'Bob'];
复制代码

如果数组成员允许包含多个类型值,则使用 (type1 | type2 | ...)[] 的语法声明。

// 此处定义了一个数组 `foo`,其成员可以是字符串,也可以是数值
let foo: (string | number)[] = ['Alex', 'Bob', 123];
复制代码

如果数组的成员是对象,则有如下两种声明方式:

// 方式 1:通过预定义好的类型,声明 `friends` 成员类型
interface Person {name: string;
}
let friends: Person[] = [ { name: 'Alex' }, { name: 'Bob' } ];// 方式 2:直接通过字面量类型的形式,声明 `friends` 成员类型
let friends: {name: string
}[] = [ { name: 'Alex' }, { name: 'Bob' } ];
复制代码

接下来,进入到扩展类型的学习。

扩展类型

Typescript 除了支持 JavaScript 类型之外,还提供了一些扩展类型。

首先,我们来介绍下字面量类型。

字面量类型

当我们像下面这样赋值时:

let seven: number = 7;
// ❌ 这样赋值的话会有错误,提示`Type '"Seven"' is not assignable to type 'number'`
seven = 'Seven';
复制代码

注意,这里的 'Seven' 被当成了一个类型,说 'Seven' 类型不能赋值给数值类型变量 seven

其实这里的 'Seven' 是一个字符串字面量类型。

Typescript 中的字面量类型包括:字符串字面量、数值字面量和布尔值字面量。

除此之外,我们还可以使用 type 关键字定义一个新的类型:

// 此处我们定义了一个新类型 `FavoriteNumber`,这个新类型仅由三个值的集合组成
type FavoriteNumber = 'One' | 'Two' | 'Seven';
// 接下来,将变量 `seven` 声明为类型 `FavoriteNumber`,并赋值为 `'Seven'`
let seven: FavoriteNumber = 'Seven';
复制代码

以上定义了一个类型 FavoriteNumber,它由三个值的集合组成(一个类型通常至少包含两个或以上的值)。变量 seven 被声明为该类型,赋值为 'Seven',这是一个有效值。如果我们给 seven 赋了一个不在 FavoriteNumber 类型之内的值,就会报错,比如:

// ❌ 此处会报错:`Type '123' is not assignable to type 'FavoriteNumber'.`
let seven: FavoriteNumber = 123;
复制代码

枚举

在介绍枚举类型之前,我们先来看下面的代码:

// 此处定义了两个变量 `errorColor` 和 `infoColor`
let dangerColor = 'red';
let infoColor = 'blue';// 添加一个判断传入颜色是否是危险色的函数
function isItDangerColor(color) {return color === 'red';
}// 接下来,调用函数 `isItRed`
isItDangerColor(dangerColor); // true
isItDangerColor(infoColor); // false
复制代码

上面这一小段的代码逻辑很简单,但有个小小的问题——如果表示危险的颜色由 'red' 变为 'pink' 了,那么我们就需要修改两个地方的代码。

针对这个问题,我们稍微修改下代码,引入一个表示颜色集合的变量 Colors 来解决:

// 我们使用 `Colors` 这个变量来存储逻辑中使用到的颜色集合
const Colors = {Danger: 'red',Info: 'blue'
};// 在余下的业务逻辑中,我们使用颜色变量代替之前的颜色字面值
let dangerColor = Colors.Danger;
let infoColor = Colors.Info;function isItDangerColor(color) {return color === Colors.Danger;
}
复制代码

这样带来的便利是,如果 Colors.Danger 所代表的颜色值变了,只要在 Colors 中修改一下就可以了。

进一步思考,可以知道,这里的 Colors.DangerColors.Info的值具体是什么并不重要,只要能保证它们彼此不相等就行。比如,我们写成下面这样:

// 这样定义 `Colors` 依旧不会影响逻辑
const Colors = {Danger: 0,Info: 1
};
复制代码

这种定义变量的方式,用 TypeScript 中的枚举来改写就是下面这样的:

// 枚举变量使用 `enum` 关键字定义
// 此处定义了一个枚举变量 `Colors`
enum Colors {Danger,Info
}
复制代码

上面一段代码经过编译后,得到的 JavaScript 源码如下:

var Colors;
(function (Colors) {Colors[Colors["Danger"] = 0] = "Danger";Colors[Colors["Info"] = 1] = "Info";
})(Colors || (Colors = {}));
复制代码

由此可知,

enum Colors {Danger,Info
}// 等价于var Colors = {0: 'Danger',1: 'Info','Danger': 0,'Info': 1
};
复制代码

我们修改下初始的例子,使用枚举来组织逻辑:

enum Colors {Danger, // 对应的值是 0Info, // 对应的值是 1Success // 对应的值是 2
}// 我们将函数 `isItDanger` 的参数 `color` 类型约束为 `Colors`
// 说明此函数只接收 `Colors` 中列举的值
function isItDanger(color: Colors): boolean {return color === Colors.Danger;
}// 接下来使用 `Colors.Info` 调用 `isItDanger` 函数
isItDanger(Colors.Info); // false
复制代码

除了使用默认的索引值,我们还可以为枚举变量中的每一项指定值:

enum Colors {Red, // 对应的值是 0Blue = 3, // 将 Blue 值指定为 3Green // 接上面的 3,此处的值是 4
}enum Colors {Red = 'red', // 对应的值是 'red'Blue = 'blue', // 对应的值是 'blue'Green = 'green' // 对应的值是 'green'
}
复制代码

上篇完。

相关文章:

SLAM前端中的视觉里程计和回环检测

1. 通常的惯例是把 VSLAM 分为前端和后端。前端为视觉里程计和回环检测,相当于是对图像数据进行关联;后端是对前端输出的结果进行优化,利用滤波或非线性优化理论,得到最优的位姿估计和全局一致性地图。 1 前端:图像数…

粗心导致的bug

不管是调试程序还是直接看输出i都为2,下面是运行时输出的: 用vs2010以前遇到更奇葩的事,这次用vs2013也是遇到奇葩,taskNumber值为2一定,下面两个循环体,每个循环体各执行一次,程序输出i 2真是…

gearman中任务的优先级和返回状态

gearman中任务的优先级和返回状态 一、任务的优先级 同步阻塞调用,等待返回结果 doLow:最低优先 doNomal:正常优先级 doHigh:最优先执行 异步派发任务,不等待返回结果,返回任务句柄,通过该句柄可获取任务运行状态信息 doLowBackgr…

VMware学习使用笔记

本人在学习基础上,结合实际项目实现总结的笔记。以下内容都是基于VMware vSphere 6.7的官方文档中vSAN的规划和部署而来,网址https://docs.vmware.com/cn/VMware-vSphere/index.html。 对于ESXi系统 对于内存不足512G,可以从USB或Micro SD引导…

【原】Java学习笔记020 - 面向对象

1 package cn.temptation;2 3 public class Sample01 {4 public static void main(String[] args) {5 // 成员方法的参数列表:6 // 1、参数列表中的数据类型是值类型7 // 2、参数列表中的数据类型是引用类型8 // A:…

win32 wmi编程获取系统信息

//GetSysInfo.h#pragma once#include <afxtempl.h>class GetSysInfo { public:GetSysInfo(void);~GetSysInfo(void);public: /********获取操作系统版本&#xff0c;Service pack版本、系统类型************/ void GetOSVersion(CString &strOSVersion,CString &…

cmake 注意事项

1. add_subdirectory()调用 CMake将在每次add_subdirectory()调用时创建一个新的变量作用域,因此这个参数最好的用法是放在cmaklists的最后使用&#xff0c;这样的话创建的新的变量的作用范围与内存的变化就不会影响到后面的变量的使用。 查看并打印在cmake里面定义的宏在&am…

Jmeter 使用自定义变量

有些情况下比如发起测试时URL的主机名和端口需要在采样器中出现多次&#xff0c;这样就有个问题&#xff0c;当测试的主机更改时&#xff0c; 我们需要修改主机名称&#xff0c;这时就需要修改多个地方&#xff0c;如果多的情况会有遗漏。如果我们在配置脚本的时候&#xff0c;…

Kubernetes1.5源码分析(二) apiServer之资源注册

源码版本 Kubernetes v1.5.0 简介 k8s里面有各种资源&#xff0c;如Pod、Service、RC、namespaces等资源&#xff0c;用户操作的其实也就是这一大堆资源。但这些资源并不是杂乱无章的&#xff0c;使用了GroupVersion的方式组织在一起。每一种资源都属于一个Group&#xff0c;而…

opencv3 视频稳像

OpneCV3.x中提供了专门应用于视频稳像技术的模块&#xff0c;该模块包含一系列用于全局运动图像估计的函数和类。结构体videostab::RansacParams实现了RANSAC算法&#xff0c;这个算法用来实现连续帧间的运动估计。videostab::MotionEstimatorBase是基类中所有全局运动估计方法…

perf+火焰图 = 性能分析利器

perf 1. perf安装 sudo apt install linux-tools-common检查是否安装好 perf如果出现 You may need to install the following packages for this specific kernel:推荐安装可以按照提示将推荐安装包全部安装好 sudo apt-get install linux-tools-对应版本-generic linux-c…

3- MySQL数据类型

MySQL表字段类型 MySQL数据表的表示一个二维表&#xff0c;由一个或多个数据列构成。 每个数据列都有它的特定类型&#xff0c;该类型决定了MySQL如何看待该列数据&#xff0c;并且约束列存放相应类型的数据。 MySQL中的列表有三种&#xff1a;数值类&#xff0c;字符串类和日期…

AddressSanitizer+cmake

1. AddressSanitizercmake(Linux) 编译指令&#xff1a; CXXFLAGS通常需要加上 -fsanitizeaddress -fno-omit-frame-pointer #打印函数调用路径 -fsanitize-recoveraddress #AddressSanitizer遇到错误时能够继续-fsanitizeaddress-fno-omit-frame-pointer-fsanitize-rec…

vibe前景提取改进算法

// improveVibeAlgorithm.h #ifndef IMPROVED_VIBE_ALGORITHM_H #define IMPROVED_VIBE_ALGORITHM_H#include <opencv2/opencv.hpp> using namespace std;#define WINSIZE 5 // Vibe改进算法, Barnich, Olivier & Droogenbroeck, Marc. (2009). // ViBE: A powerfu…

npm-debug.log文件出现原因

项目主目录下总是会出现这个文件&#xff0c;而且不止一个&#xff0c;原因是npm i 的时候&#xff0c;如果报错&#xff0c;就会增加一个此文件来显示报错信息&#xff0c;npm install的时候则不会出现。转载于:https://www.cnblogs.com/liuna/p/6558006.html

AutoFac Ioc依赖注入容器

本文原著&#xff1a;牛毅 原文路径 http://niuyi.github.io/blog/2012/04/06/autofac-by-unit-test/ 理解IOC容器请看下图&#xff1a; 没有使用IOC容器的情况下: 使用IOC容器的情况下&#xff1a; 去掉IOC容器的情况后&#xff1a; IOC容器又像一个插座&#xff0c;将电输送…

Linux安装App记录

Ubuntu18.04安装微信

windows socket编程入门示例3

// Lock.h #ifndef _Lock_H #define _Lock_H #include <windows.h>class CriticalSection { private:CRITICAL_SECTION g_cs; //临界区 public:CriticalSection();~CriticalSection();void Lock();void UnLock(); }; #endif// Lock.cpp #include "Lock.h"…

游戏开发:js实现简单的板球游戏

js实现简单的板球游戏大家好&#xff0c;本次我们来使用js来实现一个简单的板球游戏。截图如下&#xff1a;首先&#xff0c;设计页面代码&#xff0c;页面代码很简单&#xff0c;因为整个几乎是使用js编写的&#xff0c;页面几乎没有代码&#xff0c;如下&#xff1a;<!DOC…

SLAM精度测评——EVO进阶

1. 基本概念 1.1 Umeyama算法 ATE&#xff1a; evo_ape tum state_groundtruth_estimate0/data.tum orb2/CameraTrajectory.txt -r trans_part -va --plot --plot_mode xy --save_results /home/sun/evo/v1_01_easy/orb2/ate.zip RPE&#xff1a; evo_rpe tum state_groun…

python读取图片并且显示

使用python-opencv读取图片&#xff0c;利用opencv或matplotlib显示图片。 # -*- coding: utf-8 -*-import numpy as np from matplotlib import pyplot as plt #import urllib import cv2def load_image1(file):# Load an color image in grayscaleimg cv2.imread(file,0)cv…

shiro认证

shiro权限认证&#xff1a; 具体的认证流程是这样的&#xff1a; 一般流程&#xff1a; 通过.ini的文件来初始化工厂&#xff0c;.ini的文件的好处是可以创建多个组&#xff0c;而.properties的文件只能创建一组。 系统默认有shiro.ini的文件&#xff0c;但是一般我们是自定义数…

小猿圈Linux基础面试题,看看你能答对几道?

最近身边的很多朋友都在学习linux&#xff0c;从最开始的安装软件都需要百度一天的他们&#xff0c;现在已经成长为了&#xff0c;不需要百度就可以把自己弄懵圈的了&#xff0c;接下来的几天小猿圈linux老师会为大家准备一些实用的linux技巧分析给大家&#xff0c;希望对你有所…

ORB-SLAM2 论文翻译

https://ug98gs7tbw.feishu.cn/docs/doccnKKOWAjkKv7AzAiEvbnM3Tf

mxnet教程1

import mxnet as mx #%matplotlib inline import os import subprocess import numpy as np import matplotlib.pyplot as plt import tarfileimport warnings warnings.filterwarnings("ignore", categoryDeprecationWarning)# 从内存中读取数据 def test1():data …

番外:Spring MVC环境搭建和Mybatis配置避坑篇

2019独角兽企业重金招聘Python工程师标准>>> web.xml引入对spring mvc的支持&#xff1b; spring-mvc配置spring-mvc&#xff1b; spring-mybatis配置mybatis支持&#xff0c;并指名mapper文件的位置&#xff1b; mybaits-config配置mybatis&#xff1b; jdbc.prope…

50个云终端只需一台服务器是怎么一回事

看到这个标题也许有人会说50个云终端只需要一台服务器这应该是不可能的吧&#xff0c;即使是真的那这个服务器的配置和价格应该也要非常高的吧。但是如果有人和你说50个云终端只需要一台中等配置和价格的服务器就可以的呢。而且这50个用户桌面都可以正常的使用不会出现卡顿等现…

SLAM学习,小白入门到殿堂级大牛资料整理

总结一下我接触过的SLAM算法吧,主要集中在visual slam: 特征法: ORB SLAM https://github.com/raulmur/ORB_SLAM2优势: 在静态环境下定位准确,稳定, 单目和双目版本都可以达到实时(高于10frames/s)。代码可读性强,易扩展, 网上也有实现和imu融合的版本。 劣势:建的地图…

python解析json

“data.json”文件内容如下&#xff1a; {"id":"1220562","alt":"http:\/\/book.douban.com\/book\/1220562","rating":{"max":10, "average":"7.0", "numRaters":282, "min…

8.改进应用程序

2019独角兽企业重金招聘Python工程师标准>>> ##8.1数字格式设置&#xff08;storyboard&#xff09; ##8.1.2可选类型拆封 import Cocoa extension Double{var dollars : String{let formatter : NumberFormatter NumberFormatter()//NSNumberFormatter弃用var re…