基础
- 原始数据类型
- 任意值
- 类型推论
- 联合类型
- 接口
- 数组
- 函数
- 类型断言
- 申明文件
- 内置对象
原始数据类型
Javascript 的类型分为两种: 原始数据类型(Primitive data types)和对象类型(Object types)。
原始数据类型包括: 布尔值、数字、字符串、null、undefined、以及ES6中的新类型 Symbol。
布尔值
布尔类型,使用 boolean 定义
let isDone: boolean = false
// or
let createByBoolean: boolean = Boolean(1)
布尔对象, 使用Boolean 构造函数
let createByNewBoolean: Boolean = new Boolean(1)
数字
数字类型, 使用 number 定义
let digital: number = 6
let notANumber: number = NaN
let infinityNumber: number = Infinity
数字对象, 使用Number构造函数
let six: Number = new Number(6)
字符串
字符串类型,使用string定义:
let myName: String = 'Tao' // 这里不用 name 是因为 在ts name 已经被声明过,这里不能使用
// 模板字符串
let sentence: string = `Hello, my name is ${myName}`
字符串对象, 使用String定义
let myName: String = new String('Tao')
console.log(myName)
// 模板字符串
let sentence: String = new String(`Hello, my name is ${myName}`) ;
console.log(sentence)
空值
JavaScript 没有空值(Void)的概念,在 TypeScirpt 中,可以用 void 表示没有任何返回值的函数:
function alertName(): void {
alert('My name is Tao')
}
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
let unusable: void = undefined
Null 和 Undefined
在 TypeScript 中,可以使用 null 和 undefined 来定义这两个原始数据类型:
let u: undefined = undefined
let n: null = null
undefined类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null。
与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量,而 void 类型的变量不能赋值给 number 类型的变量:
let num: number = undefined
let str: string = null
// 这些都是允许的, 不会报错
任意值
- 如果是一个普通类型,在复制过程中改变类型是不背允许的,但如果是 any 类型,则云讯被赋值为任意类型
- 任意值(Any) 用来表示允许赋值为任意类型
- 在任意值上访问任何属性都是允许的,也允许调用任何方法
- 声明一个变量为任意值之后面对它的任何操作,返回的内容的类型都是任意类型
- 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let myFavoriteNumber: any = 'six'
myFavoriteNumber = 7
let anyThing: any = 'hello'
console.log(anyThing.myName)
anyThing.setName('Tao').sayHello()
类型推论
如果没有明确的指定类型, 那么 Typescript 会依照类型推论(Type Inference)的规则推断出一个类型
Typescript 2.1 中,编译器会考虑对 myFavoriteNumber 的最后一次赋值来检查类型。
let myFavoriteNumber = 'six'
// 等价于 let myFavoriteNumber: string = 'six'
联合类型
- 联合类型(Union Types)表示取值可以为多种类型中的一种。
- 联合类型使用 | 分隔每个类型
let myFavoriteNumber: string | number
myFavoriteNumber = 'six'
myFavoriteNumber = 6
这里的 string | number 的含义是, 允许 myFavoriteNumber 的类型是 string 或者 number, 但是不能是其他类型。
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。
function getString( someThing: string |number ): string {
// 这里的 函数 getString 中的第二个 : string 代表返回值类型
return someThing.toString()
}
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
接口
Typescript 中的接口是一个非常灵活的概念, 除了可以用于对类的一部分行为进行抽象以外, 也常用于对 「 对象的形状(Shape) 」进行描述。
interface Person {
name: sting,
age: number
}
使用接口类型赋值时, 变量的形状必须和接口保持一致,属性数量和类型保持一致,不允许添加未定义的属性。
可选属性,有时候我们希望不要完全匹配一个形状,那么可以用可选属性。 使用 ?
interface Person {
name: string,
age?: number,
}
let tao: Person = {
name: 'Tao'
}
任意属性,有时候我们希望一个借口允许有任意属性
interface Person {
name: string,
age?: number,
[propName: string]: any, // 使用任意属性后, 将不会对对象的任意属性长度进行限制
}
let tao: Person = {
name: 'Tao',
sex: 'male'
}
注意:一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
interface Person {
name: string,
age?: number,
[porpName: string]: string
}
let tao: Person = {
name: 'Tao',
age: 26, // err 25 是number类型, 不是string的子属性
sex: 'male'
}
// 所以通常情况下我们会将 任意属性定位 any(任意值)
只读属性,对象中的一些字段只能在创建的时候被赋值,用 readonly 定义只读属性。
注意: 只读约束存在于第一次给对象赋值的时候, 而不是第一个给只读属性赋值的时候
interface Person {
readonly id: number
}
// 第一次给对象赋值, 激活只读约束
let tao: Person = {
id: 89757, // success
}
tao.id = 9527 // err 这里会抛出异常, 因为此处已经不属于第一个个对象赋值
数组
定义数组有多种定义方式
-
「 类型 + 方括号 」表示法
let fibonacci: number[] = [1, 1, 2, 3, 5] // 这里的数组 不允许出现其他类型 fibonacci.push('8') // err let fibonacci: number[] = [1, '1', 2, 3, 5] //err
-
使用数组泛型(Generic)Array 来表示数组
let fibonacci: Array<number> = [1, 1, 2, 3, 5]
-
用接口表示数组
interface NumberArray { [index: number]: number } let fibonacci: NumberArray = [1, 1, 2, 3, 5]
-
any表示数组中允许出现任意类型
let list: any[] = ['tao', 26, { sex: 'male' }]
-
类数组(Array-like Object)不是数组类型, 比如
arguments
:事实上常见的类数组都有自己的接口定义,如
IArguments
,NodeList
,HTMLCollection
等:function sum() { let args: IArguments = arguments }
函数
函数是 javascript 中的一等公民
函数的定义方式有两种-- 函数申明(Function Declaration)和函数表达式(Function Expression):
-
函数申明(Function Declaration)
function sum(x: number, y: number): number { return x + y }
注意,输入多余的(或者少于要求的)参数,是不被允许的:
-
函数表达式(Function Expression)
let mySum = function(x: numnber, y: number): number { return x + y }
-
用接口定义函数的形状
我们也可以使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc { (source: string, subString: string): boolean } let mySearch: SearchFunc mySearch = function(source: string, subString: string): boolean { return source.search(subString) !=== -1 }
-
可选参数
之前提到, 输入多余的(或者少于要求的)参数是不被允许的, 那么如何定义可选的参数呢?
这里与接口中的可选属性类似 , 也是使用
?
表示可选参数function buildName(firstName: string, lastName?: string): string { if (lastName) { return firstName + ' ' + lastName; } else { return firstName; } } let tomcat = buildName('Tom', 'Cat'); let tom = buildName('Tom');
需要注意的是 可选参数必须接在必需参数的后面
-
参数默认值
function buildName(firstName: string, lastName: string = 'Cat') { return firstName + ' ' + lastName; } let tomcat = buildName('Tom', 'Cat'); let tom = buildName('Tom');
此时就不受「可选参数必须接在必需参数后面」的限制了
-
剩余参数(rest)
function push( array: any[], ...items: any[] ) { items.forEach( function(item) { array.push(item) } ) }
rest 参数只能是最后一个参数
-
重载
重载允许一个函数接受不同数量或类型的参数时, 作出不同的处理。
比如,我们需要实现一个函数
reverse
,输入数字123
的时候,输出反转的数字321
,输入字符串'hello'
的时候,输出反转的字符串'olleh'
。利用联合类型,我们可以这么实现:
function reverse(x: number | string): number | string { if(typeof x === 'number') { return Number( x.toString().split('').reverse().join('') ) } else { return x.split('').reverse().join('') ) } }
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个
reverse
的函数类型:function reverse(x: number): number function reverse(x: string): string function reverse(x: number | string): number | string { if(typeof x === 'number') { return Number( x.toString().split('').reverse().join('') ) } else { return x.split('').reverse().join('') ) } }
上例中,我们重复定义了多次函数
reverse
,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
语法
<类型>值
// or
值 as 类型
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种。
将一个联合类型的变量指定为一个更加具体的类型
上面说过,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number {
return something.length
}
// 报错 number 中无法访问到 length 这个属性值
而有的时候,我们确实要在还不确定类型的时候就访问其中一个类型的属性或方法,此时我们可以使用类型断言
function getLength(something: string | number): number {
if( (<string>something).length ) {
return (<string>something).length
} else {
return something.toString().lengt
}
}
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的:
function getBoolean(something: string | number): boolean {
return <boolean>something
}
申明文件
当时用第三方库时,我们需要引用它的声明文件。
申明语句:使用declare关键字来定义类型
如:我们需要使用第三方库Jquery获取一个id是foo的元素, 但在 ts 中, 编译器并不知道 $
或者 jQuery
是什么东西。此时,我们需要使用 declare var
来定义它的类型
declare var jQuery: ( selector: string ) => any
jQuery('#foo')
什么是申明文件
通常我们会把声明语句放到一个单独的文件 (jQuery.d.ts)
中, 这就是声明文件
// jQuery.d.ts
declare var jQuery: ( selector: string ) => any
// src/index.ts
jQuery('#foo')
一般来说,ts 会解析项目中所有的 *.ts
文件,当然也包含以 .d.ts
结尾的文件。所以当我们将 jQuery.d.ts
放到项目中时,其他所有 *.ts
文件就都可以获得 jQuery
的类型定义了。
假如仍然无法解析,那么可以检查下 tsconfig.json
中的 files
、include
和 exclude
配置,确保其包含了 jQuery.d.ts
文件。
三斜线指令
三斜线指令 类似于声明文件中的 import
,它可以用来导入另一个声明文件。与 import
的区别是,当且仅当在以下几个场景下,我们才需要使用三斜线指令替代 import
:
-
当我们在书写一个全局变量的声明文件时
-
当我们需要依赖一个全局变量的声明文件时
这些场景听上去很拗口,但实际上很好理解
场景一: 在全局变量的声明文件中,是不允许出现 import
, export
关键字的。
场景二: 当我们需要依赖一个全局变量的声明文件时,由于全局变量不支持通过 import
导入,当然也就必须使用三斜线指令来引入了
// types/node-plugin/index.d.ts
/// <reference types="node" />
export function foo(p: NodeJS.Process): string
// src/index.ts
import { foo } from 'node-plugin';
foo(global.process)
第三方声明文件
jQuery 的声明文件, 其实社区已经帮我做好了,jQuery in DefinitelyTyped。我们可以直接下载下来使用, 在使用时我们并不需要在去进行声明
这里更加推荐的是 使用 @types
来统一管理第三库的声明文件。
以 jQuery 为例, 使用方式如下:
npm i @types/jquery
内置对象
JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。