文章目录
基本类型
基本数据类型
ts 支持与 js 几乎相同的数据类型,但是增加对数据类型的限制
let isDone: boolean = false;
let name: string = "chen";
let list: number[] = [1, 2, 3];
let sentence: string = `Hello, my name is ${name}`;
let str = "abc";
str = 123; // 类型不可以转换的错误
元组
新增元组类型来解决一个数组中包含不同类型的情况
let x: [string, number];
x = ['hello', 10];
枚举
新增枚举类型,可以使用枚举类型对一组数组赋予别名
enum Color{Red, Green, Blue}
let c: Color = Color.Green;
默认情况下,枚举类型从0开始元素编号,支持手动指定成员的数值
enum Color{Red = 1, Green, Blue};
let c: Color = Color.Green;
枚举类型支持通过枚举的值来得到对应的名字
enum Color{Red = 1, Green, Blue};
console.log(Color[2]); // Green
Any
不能判断一个变量的具体类型时候,可以使用 Any , 效果将会与 js 中相似,支持不同数据类型之间的赋值
let notSure: any = 4;
notSure = 'maybe a string indeed';
这个类型同样对数组有效
let list: any[] = [1, true, "free"];
list[1] = 100;
Void
void 类型一般用于声明函数没有返回值
function warnUser(): void{
console.log("This is my warning message");
}
声明一个void类型的变量没有什么大用,因为你只能为它赋予 undefined 和 null
let unusable: void = undefined;
null和undefined
和js中的 null 和 undefined 相似,但当在 tsconfig.js 文件中设置 strictNullChecks 为 true 时,就不能将 null 和 undefined 赋值给除它们自身和 void之外的任意类型
never
never 类型表示的是那些永不存在的值的类型,例如抛出异常或者死循环的函数,或者是永不为真的变量,never 类型可以是任何类型的子类型,也可以赋值给任何类型
// 抛出错误的函数
function error(message: string): never{
throw new Error(message);
}
// 死循环的函数
function infiniteLoop(): never{
while(true){
}
}
Object
和 js 中相似
类型断言
可以理解成其他语言中的强转,typescript将不再进行特殊的数据检查和解构,它没有运行时的影响,只是在编译阶段起作用
类型断言有两种形式写法,其一是"尖括号"语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
另一个为as语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
变量声明
因为typescript是javascript的超集,所以它本身就支持let和const,其中变量声明的方式也和js相似,这里不作进一步说明
接口
typescript的核心原则之一是对值所具有的结构进行类型检查。在typescript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约
接口初探
接口可以用作规范函数中一些必须的参数,如下两个例子,效果是等价的
function printLabel(labelledObj: {label: string}) {
console.log(labelledObj.label);
}
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
可选属性
接口里的属性不全是必需的,带有可选属性的接口与普通的接口定义区别在于在可选属性名字定义的后面加一个?符号
interface SquareConfig{
color?: string;
width?: number;
}
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值,你可以在属性名前用readonly来指定只读属性:
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = {x: 10, y: 20};
p1.x = 5; // error!
readonly 和 const
最简单判断该用readonly还是const的方法是看要把它作为变量使用还是作为一个属性,作为变量使用的话用const,作为属性则使用readonly
额外的属性检查
如果需要有除定义的函数变量之外的场景,可以添加一个字符串索引签名
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any; // 字符串索引签名
}
如果不加字符串索引签名,不可以定义除color, width以外的属性
函数类型
除了描述带有属性的普通对象外,接口也可以描述函数类型
interface SearchFunc {
(source: string, subString: string): boolean; // 确定函数的返回值类型为boolean类型
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
可索引的类型
接口支持描述那些能够“通过索引得到”的类型,比如a[10]
interface StringArray {
[index: number]: string;
}
typescript支持number和string两种索引签名,有个问题要注意,number类型的索引返回值需是返回值类型的子类型,因为number索引时,js会将它先转换成string再去索引,有可能出现与string索引重复的情况
class Animal{
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
类类型
接口可以明确强制一个类去符合某种契约(比如变量或者函数)
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 Shape{
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>();
square.color = 'blue';
square.sideLength = 10;
一个接口可以继承多个接口,创建出多个接口的合成接口
interface Shape{
color: string;
}
interface PenStroke{
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
类
typescript中的类和ES6中的比较相似,这里介绍一些它们之间不同的地方
共有修饰符
在typescrip中,成员都默认为public,当然也可以明确的将一个成员标记为public
私有修饰符
当成员被标记成private时,它就不能在声明它的类的外部访问
受保护修饰符
protected修饰符与private修饰符的行为很相似,但有一点不同,protected成员在派生类中仍然可以访问
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
}
let howard = new Employee('Howard', 'Sales');
console.log(howard.name); // 错误,类外不能引用
readonly属性
可以使用readonly关键字将属性设置为只读的,只读属性必需在声明时或构造函数里被初始化
存取器
typescript支持通过getters/setters来截取对象对象成员的访问。
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if(passcode && passcode == "secret passcode") {
this._fullName = newName;
}else {
console.log("Error: Unauthorized update of employee!");
}
}
}
静态属性
类的静态成员存在于类本身上面而不是类的实例上,在这个例子里,我们使用static定义origin,因为它是所有网格都会用到的属性,如同在实例属性上使用this,前缀来访问属性一样,这里我们使用Grid.来访问静态属性
class Grid {
static origin = {x: 0, y: 0};
getOrigin(){
return Grid.origin.x;
}
}
抽象类
抽象类作为其他派生类的基类使用。它们一般不会直接被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
abstract class Animal {
abstract makeSound(): void; // 若继承必须实现makeSound方法
move(): void {
console.log('roaming the earch...');
}
}
函数
函数类型
typescript支持给每个参数添加类型之后再为函数本身添加返回值类型
function add(x: number, y: number): number {
return x + y;
}
书写完整函数类型
函数的类型由参数类型和返回值组成,书写完整的函数类型以保证API信息的完整
可选参数
typescript中传递给一个函数的参数个数必须与函数期望的参数个数一致,在javascript中,每个参数都是可选的,可传可不传,在typescrip中要实现类似的功能,可以在参数名旁使用?实现可选参数的功能
function buildName(firstName: string, lastName?: string) {
if(lastName) {
return firstName + " " + lastName;
}else {
return firstName;
}
}
可选参数必须跟在必须参数后面,如果上例想让first name可选的,那么就必须调整它们的位置,把first name放在后面。
默认参数
typescript可以为参数提供一个默认值
function buildName(firstName: string, lastName = "Smith") {
return firstName + "" + lastName;
}
剩余参数
如果想同时操作多个参数,或者你并不知道会有多少参数传递进来,可以在…后面给定名字来定义这样剩余参数
function buildName(firstName: string, ... restOfName: string[]) {
return firstName + " " + restOfName.join("");
}
let employeeName = buildName("joseph", "samuel", "lucas", "mackinzie");
重载
为了让编译器能够选择正确的检查类型,它与javascript里的处理流程相似。它查找重载列表,尝试使用第一个重载定义。如果匹配的话就使用这个。因此,在定义重载的时候,一定要把最精确的定义放在最前面。
function pick(x; {suit: string; card: number;}[]): number;
funciton pick(x: number): {suit: string; card: number;};
function pick(x): any {}
泛型
对于大型系统时,需要考虑组件API的可重用性,组件不仅要能够支持当前数据类型,同时还需要支持未来的数据类型,所以引入泛型的概念来创建可重用的组件
可能有些同学会觉得可以使用any类型,但是这样会导致类型的不确定性,你给到一个number类型的参数希望结果也是number类型,any类型并不能实现这个效果
** 泛型的最大特点在于通过变量来表示类型,而不是具体的值 **
泛型接口
function identity<arg: T>: T{
return arg;
}
泛型类
class GenericNumber<T> {
zeroValue: T;
add: {x: T, y: T} => T;
}
泛型约束
当需要操作某个类型的某个属性,比如数组的length,但是因为用的是泛型,并不能确定这个属性是否有length,这里可以定义一个接口来描述约束条件,创建一个包含.length属性的接口,使用这个接口和extends关键字来实现约束
interface Lengthwise{
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T{
console.log(arg.length);
return arg;
}
在泛型约束中使用类型参数
可以声明一个类型参数,且它被另一个类型参数所约束,比如我们想要用属性名从对象里获取这个属性,且要确保这个属性存在于对象obj上
function getProperty(obj: T, key: K){
return obj[key];
}
在泛型里使用类类型
在Typescript使用泛型创建工厂函数时,需要引用构造函数的类类型
function create<T>(c: {new(): T;}): T {
return new c();
}
枚举
使用枚举可以定义一些带名字的常量,可以清晰地表达意图或者创建一组有区别的用例,typescript支持数字和基于字符串的枚举
数字枚举
通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型
enum Response {
No = 0,
Yes = 1
}
function respond(recipient: string, message: Response): void {
// ...
}
respond("Princess Caroline", Response.Yes);
字符串枚举
enum Direction {
Up = "Up",
Down = "Down",
Left = "Left",
Right = "Right"
}
混合枚举
从技术的角度来说,枚举可以混合字符串和数字成员,但是不建议这么做
计算和常量成员
每个枚举成员都带有一个值,它可以是常量或计算出来,当满足如下条件时,枚举成员被当作是常量:
- 它是枚举的第一个成员且没有初始化器,这种情况下它被赋予值0
enum E{x}
- 它不带有初始化器且它之前的枚举成员是一个数字常量。这种情况下,当前枚举成员的值为它上一个枚举成员的值加1
enum E{
A = 1,B,C
}
- 枚举成员使用常量枚举表达式初始化。常数枚举表达式是typescript表达式的子集,它可以在编译阶段求值。
类型兼容性
与js中不同的,typescript的object的类型会区分各种各样的类,并不能像js中随意赋值
基本规则
如果x要兼容要,那么y至少具有与x相同的属性,即y是否能赋值给x,编译器检查x中的每个属性,看是否能在y中也找到对应属性
高级类型
交叉类型
把多种类型叠加到一起成为一种类型,下面是如何创建混入的一个简单例子:
function extend<T, U>(first: T, second: U): T&U {
let result = <T & U>{};
for(let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for(let id in second) {
if(!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
模块
typescript与ES5一样,任何包含顶级import或者export的文件都被当作一个模块。相反地,如果一个文件不带顶级的import或者export声明,那么它的内容被视为全局可见的
命名空间
我们需要一种手段来组织代码,以便于在记录它们类型的同时还不用担心与其它对象产生命名冲突。因此,我们把验证器包裹到一个命名空间内,而不是把它们放在全局命名空间下
分离到多文件
当应用变得越来越大时,我们需要将代码分离到不同的文件中以便于维护
// Validation.js
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
别名
另一种简化命名空间操作的方法是使用import q = x.y.z;
给常用的对象起一个短的名字
namespace Shapes {
export namespace Polygons {
export class Triangle{};
export class Square{};
}
}
import polygons = Shapes.Polygons;
let sg = new polygons.Square();
声明合并
"声明合并"是指编译器将针对同一个名字的两个独立声明合并为单一声明。合并后的声明同时拥有原先两个声明的特性,任何数量的声明都可被合并;不局限两个声明
合并接口
最简单也是最常见的合并类型是接口合并。从根本上说,合并的机制是把双方的成员放到一个同名的接口里。
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
这两个接口将合并成一个声明:
interface Box {
height: number;
width: number;
scale: number;
}
合并命名空间
同接口
非法合并
typescript并非允许所有的合并。目前,类不能与其它类或者变量合并
小伙伴们今天的学习就到这里了,如果觉得本文对你有帮助的话,欢迎转发,评论,收藏,点赞!!!
每天学习进步一点点,就是领先的开始。如果想继续提高,欢迎关注我,或者关注公众号”祯民讲前端“。大量前端技术文章,面试资料,技巧等助你更进一步!