**注意:**本文章不能作为新手入门教程使用,仅做为查阅
参考文章来源:
TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。
简单的说,它就是针对弱类型语言JavaScript的一种强类型约束
以下命令会在全局环境下安装 tsc
命令,安装完成之后,我们就可以在任何地方执行 tsc
命令了。
npm install -g typescript
编译一个 TypeScript 文件很简单,我们约定使用 TypeScript 编写的文件以 .ts
为后缀
tsc hello.ts
新建一个hello.ts,输入以下代码:
function sayHello(person: string) {
return 'Hello, ' + person;
}
let user = 'Tom';
console.log(sayHello(user));
使用命令tsc hello.ts
编译后产生js文件,代码如下
function sayHello(person) {
return 'Hello, ' + person;
}
var user = 'Tom';
console.log(sayHello(user));
在 TypeScript 中,我们使用 :
指定变量的类型,:
的前后有没有空格都可以。
上述例子中,我们用 :
指定 person
参数类型为 string
。但是编译为 js 之后,并没有什么检查的代码被插入进来。
这是因为 TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。
Typescript不仅支持Javascript原有的数据类型,而且还新增了几个数据类型,不过当然仅限于在ts中,编译后还是js的那几个类型
数据类型 | 说明 |
---|---|
boolean | 布尔值 |
number | 数值类型 |
string | 字符串类型 |
Array | 数组类型 |
tuple | 元组类型(属于数组的一种,即数组中每一个元素指定类型) |
enum | 枚举类型 |
any | 任意类型,变量赋值不受类型限制(注意:尽量少用这个类型) |
undefined | 未定义类型 |
null | 空值 |
void | 多作用方法上,表示无返回值 |
never | 代表从来不会出现的值,也就是指上面数据类型之外的类型 |
// 1.定义一个boolean类型,显式声明值,不然会出警告
let flag:boolean = true;
console.log(flag);
// 2.定义一个number类型,只能接受数字类型
let num:number = 123;
let floatNum:number = 12.3;
console.log(num,floatNum);
// 3.定义一个字符串类型
let str:string = "this is a string";
console.log(str);
// 4.定义一个数组类型
// 表示数组元素只能包含数值类型
let arr:number[] = [1,2,3,14.5];
// 定义方式写法2,使用泛型定义
let arr2:Array<string> = ['a','b','c'];
console.log(arr,arr2);
// 5.定义一个元组类型,可以为每一个元素指定具体类型
let tupleArr:[boolean,number,string] = [false,123,'abc'];
console.log(tupleArr);
// 6.定义一个枚举类型,声明枚举类型使得常量更加安全,更具有语义化
// 支付状态
enum PayState{
success = 1, /* 支付成功 */
error = 2, /* 支付失败 */
nopay = 3 /* 未支付 */
}
console.log(PayState.success);
// 未指定值时,默认值就是元素下标
enum Color {Red,Green,Blue}
let a:Color = Color.Red;
console.log(a,Color.Green,Color.Blue);
// 7.定义一个any类型,变量不受限制
let anyVariable:any = 123;
anyVariable = "anyVariable";
console.log(anyVariable);
// 8.定义一个无具体值的类型,这类类型是联合类型
// 一个元素可能是number类型,可能是null或者undefined
let variable:number | null | undefined;
console.log(variable);
// 9.void定义方法返回值
// 无返回值
function noReturnValue():void {
console.log('running...')
}
// 有返回值
function returnValue():number {
return 1;
}
console.log(noReturnValue());
console.log(returnValue());
// 10.never从不会出现的数据类型
let neverValue:never;
在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):
// 函数声明(Function Declaration)
function sum(x, y) {
return x + y;
}
// 函数表达式(Function Expression),匿名函数
let mySum = function (x, y) {
return x + y;
};
function isNumber():number {
return 123;
}
function noValue():void {
console.log('无返回值函数');
}
let func = function ():number {
return 111;
};
console.log('TS匿名函数返回值',func());
function hasParam(name:string,age:number) {
console.log(`${name} --- ${age}`)
}
hasParam('zhangsan',20);
let hasParamAnon = function (name:string,age:number):string {
return name+age;
};
console.log(hasParamAnon('xiaoming',18));
function customerParamFunc(name:string,age?:number) {
if(age){
console.log('姓名:',name,'年龄:',age);
}else {
console.log('姓名:',name,'年龄保密');
}
}
customerParamFunc('xiaoming');
function defaultValue(name:string,age:number=18) {
console.log(name,'最低年龄:',age);
}
defaultValue('xiaohong');
function addNumber(...num:number[]):number {
let sum:number = 0;
for(let i=0;i<num.length;i++){
sum = sum + num[i];
}
return sum;
}
console.log('相加结果为:',addNumber(1,2,3,4));
// ES5中不支持函数重载,同名函数下面会覆盖上面的
// ts可以声明同名函数
function getInfo(name:string):string;
function getInfo(age:number):string;
function getInfo(str:any):any{
if(typeof str ==="string"){
return "我叫:"+ str;
}else{
return "我的年龄是:" + str;
}
}
虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。
new
生成Cat
和 Dog
都继承自 Animal
,但是分别实现了自己的 eat
方法。此时针对某一个实例,我们无需了解它是 Cat
还是 Dog
,就可以直接调用 eat
方法,程序会自动判断出来应该如何执行 eat
public
表示公有属性或方法TS声明一个类
class Person {
// 定义字段属性
name:string;
age:number;
sex:string;
// 构造方法
constructor(name:string,age:number,sex:string) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 普通方法
run():void{
console.log('普通方法','is running...')
}
// setter,getter方法
set setName(name:string){
this.name =name;
}
get getName():string{
return this.name;
}
set setSex(sex:string){
this.sex = sex;
}
get getSex():string{
return this.sex;
}
}
let p1 = new Person('小明',20,'');
p1.setName = '小刚';
p1.setSex = '男';
console.log(p1);
使用extends关键字即可
class Student extends Person{
constructor(name:string,age:number,sex:string) {
super(name,age,sex);
}
// 方法覆盖
run(): void {
// super.run();
console.log('子类方法','is running...')
}
}
let stu1 = new Student('小李',16,'男');
stu1.run();
不能被实例化,子类继承后必须实现抽象类中的方法
abstract class Animal {
public name:string = '';
protected constructor(name:string) {
this.name = name;
}
abstract eat():any;
}
class Dog extends Animal{
constructor(name:string) {
super(name);
}
// 普通方法
run(){
console.log(this.name,'dog is running');
}
// 必须实现抽象类中的方法
eat(): any {
console.log(this.name,'dog is eating');
}
}
let dog = new Dog('中华田园犬');
dog.run();
dog.eat();
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements
关键字来实现。这个特性大大提高了面向对象的灵活性。
举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它
interface Alarm {
alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一个类可以实现多个接口:
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
接口与接口之间可以是继承关系,这很好理解,LightableAlarm
继承了 Alarm
,除了拥有 alert
方法之外,还拥有两个新方法 lightOn
和 lightOff
。
interface Alarm {
alert(): void;
}
interface LightableAlarm extends Alarm {
lightOn(): void;
lightOff(): void;
}
【参考:https://ts.xcatliu.com/advanced/generics.html】
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
首先,我们来实现一个函数 createArray
,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
上例中,我们使用了之前提到过的数组泛型来定义返回值的类型。
这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型:
Array
允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value
的类型。
这时候,泛型就派上用场了:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
上例中,我们在函数名后添加了
,其中 T
用来指代任意输入的类型,在后面的输入 value: T
和输出 Array
中即可使用了。
接着在调用的时候,可以指定它具体的类型为 string
。当然,也可以不手动指定,而让类型推论自动推算出来:
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
定义泛型的时候,可以一次定义多个类型参数,本例中,我们定义了一个 swap
函数,用来交换输入的元组。
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.
上例中,泛型 T
不一定包含属性 length
,所以编译的时候报错了。
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length
属性的变量。这就是泛型约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
上例中,我们使用了 extends
约束了泛型 T
必须符合接口 Lengthwise
的形状,也就是必须包含 length
属性。
此时如果调用 loggingIdentity
的时候,传入的 arg
不包含 length
,那么在编译阶段就会报错了:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity(7);
// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
多个类型参数之间也可以互相约束:
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
上例中,我们使用了两个类型参数,其中要求 T
继承 U
,这样就保证了 U
上不会出现 T
中不存在的字段。
使用泛型约束接口传入的类型,之前学习过,可以使用接口的方式来定义一个函数需要符合的形状:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
当然也可以使用含有泛型的接口来定义函数的形状:
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
进一步,我们可以把泛型参数提前到接口名上:
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
注意,此时在使用泛型接口的时候,需要定义泛型的类型
与泛型接口类似,泛型也可以用于类的类型定义中:
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
【参考:https://www.cnblogs.com/chenwenhao/p/12392538.html】
告诉编译器,某个项(对象,变量,包,类等等)已经存在了,不用检查。上面已经说过了,不再复述。
对于一个你要发布的包,IDE在获得声明文件后,可以为程序员做代码提示。
例如,VS Code可以拿来做语法提示。如果你声明了一个对象A,A里面有b方法,c方法,分别是哪些参数等等。程序员引用包后,如果这个包里有声明文件被VS Code找到的话,(package.json里的types字段说明一下)程序员在调用这个A对象里,会有代码提示列表,b,c方法就列出来了。
当然,因为声明文件说明了你的包的抽象细节,各方面都可以拿来用
声明应该是纯粹对于一个标识符类型或外观的描述,便于编译器识别,外部声明具有以下特点:
declare
修饰外部声明// 声明a为一个数字
declare let a: number;
// 错误,外部声明不能初始化
// error TS1039: Initializers are not allowed in ambient contexts
declare let b: number = 2;
// 声明T为一个接口
declare interface T {}
// 声明接口类型变量b
let b: T;
// 声明fn为一个函数
// 错误,声明包含了函数实现
// error TS1183: An implementation cannot be declared in ambient contexts
declare function fn(){}
// 正确,不包含函数体实现
declare function fn(): void;
// 声明myFunc为一个函数
declare let myFunc: (a: number) => void;
// 声明MyEnum枚举类型
declare enum MyEnum {
A, B
}
// 声明NS为命名空间
declare namespace NS {
// 错误,声明不能初始化
// error TS1039: Initializers are not allowed in ambient contexts
const a: number = 1;
// 正确,仅包含声明
const b: number;
// 正确,函数未包含函数体实现
function c(): void;
}
// 声明一个类
declare class Greeter {
constructor(greeting: string);
greeting: string;
showGreeting(): void;
}
外部声明还可以用于声明一个模块,如果一个外部模块的成员要被外部访问,模块成员应该用 export
声明导出:
declare module 'io' {
export function read(file: string): string;
export function write(file: string, data: string): void;
}
declare var
声明全局变量declare function
声明全局方法declare class
声明全局类declare enum
声明全局枚举类型declare namespace
声明(含有子属性的)全局对象interface
和 type
声明全局类型在所有的声明语句中,declare var
是最简单的,如之前所学,它能够用来定义一个全局变量的类型。与其类似的,还有 declare let
和 declare const
,使用 let
与使用 var
没有什么区别
// src/jQuery.d.ts
declare let jQuery: (selector: string) => any;
// src/index.ts
jQuery('#foo');
// 使用 declare let 定义的 jQuery 类型,允许修改这个全局变量
jQuery = function(selector) {
return document.querySelector(selector);
};
declare function
用来定义全局函数的类型。jQuery 其实就是一个函数,所以也可以用 function
来定义:
// src/jQuery.d.ts
declare function jQuery(selector: string): any;
// src/index.ts
jQuery('#foo');
当全局变量是一个类的时候,我们用 declare class
来定义它的类型
// src/Animal.d.ts
declare class Animal {
name: string;
constructor(name: string);
sayHi(): string;
}
// src/index.ts
let cat = new Animal('Tom');
使用 declare enum
定义的枚举类型也称作外部枚举(Ambient Enums),举例如下
// src/Directions.d.ts
declare enum Directions {
Up,
Down,
Left,
Right
}
// src/index.ts
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
与其他全局变量的类型声明一致,declare enum
仅用来定义类型,而不是具体的值。
Directions.d.ts
仅仅会用于编译时的检查,声明文件里的内容在编译结果中会被删除。它编译结果是:
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
其中 Directions
是由第三方库定义好的全局变量。