TypeScript快速入门

2,594 阅读11分钟

#TypeScript简介

定义

总所周知,JavaScript语言并不是一门面向对象的语言,而是一种解释性的函数式编程语言。在前端Web还不是很复杂的情况下,使用JavaScript是可以应付各种需求的,但当前端页面变的越来越复杂时,JavaScript就显得比较力不从心了,而TypeScript就是为了解决这个情况而诞生的语言。 TypeScript是面向对象的语言同时支持许多面向对象的特性,因此可以使用它创建出更加强壮和易于扩展的程序。同时,TypeScript 扩展了 JavaScript 的语法,所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下工作。

根据维基百科的定义:TypeScript是一种由微软开发的自由和开源的编程语言,它是JavaScript的一个严格超集,并添加了可选的静态类型和基于类的面向对象编程。

TypeScript的优势

可以看到,越来越多的前端框架开始使用TypeScript,那么它究竟有哪些优点呢?下面罗列一些常见的优点:

  • 更多的规则和类型限制,让代码预测性更高,可控性更高,易于维护和调试。
  • 对模块、命名空间和面向对象的支持,更容易组织代码开发大型复杂程序。
  • TypeScript 的编译步骤可以捕获运行之前的错误。
  • Angular2+ 和 Ionic2+ 默认使用TypeScript,同时Vue.js和React.js等一些流行的前端框架也开始支持TypeScript。
  • ...

环境搭建

俗话说,“工欲善其事,必先利其器”,学习一门新的语言和技术必须先了解其开发环境。

安装TypeScript

TypeScript提供了两种主要的方式获取TypeScript工具:

  • 通过npm(Node.js包管理器)
  • 安装Visual Studio的TypeScript插件

最新版的Visual Studio 2017和Visual Studio 2015 Update 3默认包含了TypeScript,如果你的Visual Studio还不支持TypeScript,可以使用Visual Studio下载页面链接来获取安装插件。同时,针对使用npm的用户,可以使用下面的命令来安装TypeScript工具。

npm install -g typescript

除了上面两种方式外,我们还可以使用TypeScript提供的在线环境来体验TypeScript的魅力:www.typescriptlang.org/play/index.…

创建TypeScript文件

打开编辑器,将下面的代码输入到greeter.ts文件里。

function greeter(person) {
    return "Hello, " + person;
}

let user = "jack ma";

document.body.innerHTML = greeter(user);

编译代码

TypeScript使用.ts作为扩展名,但是这段代码仅仅是JavaScript而已,想要运行这段代码,还需要编译上面的代码。在命令行上,运行TypeScript编译器:

tsc greeter.ts

输出结果为一个greeter.js文件,它包含了和输入文件中相同的JavsScript代码。此时,我们就可以运行这段代码了。

类型注解

TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。 在这个例子里,我们希望 greeter函数接收一个字符串参数,那么我们可以这么做:

function greeter(person: string) {
    return "Hello, " + person;
}

let user = [0, 1, 2];

document.body.innerHTML = greeter(user);

重新编译,会看到一个错误:

greeter.ts(7,26): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

接口

让我们开发这样一个示例:使用一个接口,它描述了具有firstName和lastName字段的对象。在TypeScript中,如果两个类型其内部结构兼容,那么这两种类型兼容。这使我们实现一个接口,仅仅只需必要的结构形状,而不必有明确的implements子句。

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = { firstName: "Jane", lastName: "User" };

document.body.innerHTML = greeter(user);

TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。让我们创建一个Student类,它带有一个构造函数和一些公共字段。

class Student {
    fullName: string;
    constructor(public firstName, public middleInitial, public lastName) {
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
}

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person : Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = new Student("Jane", "M.", "User");

document.body.innerHTML = greeter(user);

重新运行tsc greeter.ts,你会看到生成的JavaScript代码和原先的一样。 TypeScript里的类只是JavaScript里常用的基于原型面向对象编程的简写。

语法特性

和JavaScript相比,TypeScript带来了诸多语法上的变化,下面就其比较重要的罗列如下。

字符串特性

多行字符串

使用``包裹跨行的字符串,示例:

var html = `<div>
<span></span>
</div>`

字符串模板

可以在多行字符串中使用模板,示例:

var names = 'daocheng';
function getImg() {
  return '<i></i>'
}

var html = `<div>${names}
<span>${getImg()}</span>
<div>
`

###自动拆分字符串

function getData(template, name, age) {
    console.log(template);
    console.log(name);
    console.log(age);
}

var names = 'daocheng';
var age = 23;
getData`你好,我的名字是${names},我今年${age}岁了`

参数

参数类型

Typescript中的参数类型包括: boolean/number/string/array/tuple/enum/any/(null和undefined)/ void /never。 其中元祖(tuple)、枚举、任意值、void类型和never是有别于Javascript的特有类型。

类型声明与默认参数

在Typescritpt中声明变量,需要加上类型声明,如boolean或string等。通过静态类型约束,在编译时执行类型检查,这样可以避免一些类型混用的低级错误。示例:

var names = 'daocheng';
function getData(name: stirng, age: number): boolean {
    return true
}

Typescript还支持初始化默认参数。如果函数的某个参数设置了默认值,当该函数被调用时,如果没有给这个参数传值或者传值为undefined时,这个参数的值就是设置的默认值。示例:

function max(x: number, y: number = 4): number {
    return x > y ? x : y;
}
let result1 = max(2); //正常
let result2 = max(2, undefined); //正常
let result3 = max(2, 4, 7); //报错
let result4 = max(2, 7); //正常

可选参数

在javascript里,被调用函数的每个函数都是可选的,而在typescript中,被调用的每个函数的每个参数都是必传的。在编译时,会检查函数每个参数是否传值。简而言之,传递给一个函数的参数个数必须和函数定义的参数个数一致。例如:

function max(x: number, y: number) {
    if(y){
        return x > y ? x : y;
    } else {
        return x
    }
}
let result1 = max(2);
let result2 = max(2, 4, 7); //报错
let result3 = max(2, 4);
//注意:可选参数必须放在默认参数后面

函数

剩余函数

当需要同时操作多个参数,或者并不知道会有多少参数传递进来时,就需要用到Typescript 里的剩余参数。示例:

function sum(x: number, ...restOfNumber: number[]){
    let result = x;
    restOfNumber.forEach(value => result += value);
    return result;
}
let result1 = sum(1, 2, 3, 4, 5, 6);
console.log(result1);

let result2 = sum(2);
console.log(result2);

let result3 = sum(2, 5);
console.log(result3);

generator函数

控制函数的执行过程,可以手动的干预函数执行。示例:

function getPrice(stock) {
    while (1) {
        yield Math.random() * 100;
    }
}
var priceGenerator = getPrice('dcc');
var limitPrice = 51;
var price = 100;
while (price > limitPrice) {
    price = priceGenerator.next().value;
    console.log(`this generator return ${price}`);
}
console.log(`buying at ${price}`);

析构表达式

析构表达式又称解构,是ES6的一个重要特性,Typescript在1.5版本中开始增加了对结构的支持,所谓结构,就是将声明的一组变量与相同结构的数组或者对象的元素数值一一对应。分数组解构([])和对象解构({})两种。

数组解构

let imput = [1, 2];
let [first, second] = input;
console.log(first); //相当于inputp[0]
console.log(second); //相当于input[1]
function f([first, second]) {
    console.log(first + second)
}
f{[1, 3]}   //结果是4

let [first, ...rest] = [1, 2, 3, 4];
console.log(first); //1
console.log(second); //[2,3,4]

对象解构

let test = {
    x: 0,
    y: 0,
    width: 15,
    heights: {
        height1: 10,
        height2: 20
    }
};
let { x, y: myY, width, heights: {height2} } = test;
console.log(x, myY, width, height2); //输出:0,10,15,20

箭头表达式

用来声明匿名函数,消除传统匿名函数的this指针问题。例如:

function Test1(names: string) {
    this.names = names;
    setInterval(function() {
        console.log('my name is ' + this.names);
    }, 1000)
}
function Test2(names: string) {
    this.names = names;
    setInterval(() => {
        console.log('my names is ' + this.names)
    }, 1000)
}

var a = new Test1('daocheng'); //undefined
var b = new Test2('daocheng'); //daocheng

循环

typescritpt中涉及三种高级循环方式:forEach()、for in、for of。

forEach

var myArray = [1, 2, 3, 4];
myArray.name = 'daocheng';

myArray.forEach(value => console.log(value)); //结果为1,2,3,4
//特点:不支持break,会忽略(name)

for in

var myArray = [1, 2, 3, 4];
myArray.name = 'daocheng';

for (var n in myArray ) {
    console.log(n)
}   //结果为1,2,3,4
//特点: 循环的结果是对象或者数组的键值。可以break

for of

var myArray = [1, 2, 3, 4];
myArray.name = 'daocheng';

for (var n of myArray) {
    console.log(n)
}   //结果是1,2,3,4
//特点:忽略属性,可以打断。当循环为字符串时,会把字符串中每个字符打出来

传统的JavaScript程序使用函数和基于原型(Prototype)继承来创建可重用的“类”,这对于习惯了面向对象编程的开发者来说不是很友好,Typescript中可以支持基于类(class)的面向对象编程。

类的声明

class Car {
    engine: string,
    constructor(engine: string) { 
        this.engine = engine;
    }
    drive(distanceInMeters: number = 0) { 
        console.log(`aaa is running` + this.engine)
    }
}

let car = new Car('petrol');
car.drive(100)

类的封装、继承、多态

封装、继承、多态是面向对象的三大特性。上面的例子把汽车的行为写到一个类中,即所谓的封装。在Typescript中,使用extends关键字可以方便的实现。例如:

继承

继承就是类与类之间一种特殊与一般的关系,可以理解成“is a”的关系。在继承关系中,子类可以无条件的继承父类的方法和属性。

class Car {
    engine: string;
    constructor(engine: string) {
        this.engine = engine;
    }
    drive(distanceInMeter: number = 0){
        console.log(`A car runs ${distanceInMeter}m
        powered by` + this.engine)
    }
}

class MotoCar extends Car {
    constructor(engine: string) {
        super(engine)
    }
}

let tesla = new MotoCar('electricity');
tesla.drive();
//其中子类MotoCar的实例对象tesla调用了父类Car的drive()方法。

多态

多态就是通过对传递的参数判断来执行逻辑,即可实现一种多态处理机制。

class Car {
    engine: string;
    constructor(engine: string) {
        this.engine = engine;
    }
    drive(distanceInMeter: number = 0){
        console.log(`A car runs ${distanceInMeter}m
        powered by` + this.engine)
    }
}

class Jeep extends Car {
    constructor(engine: string) {
        super(engine)
    }
    drive(distanceInMeters: number = 100) {
        console.log('jeep...')
        return super.drive(distanceInMeters);
    }
}
let landRover: Car = new Jeep('petrol'); //实现多态

Jeep子类中的drive()方法重写了Car的drive()方法,这样drive()方法在不同的类中就具有不同的功能,这就是多态。注意:子类和派生类的构造函数中必须调用super(),它会实现父类构造方法。

参数属性

参数属性是通过给构造函数的参数添加一个访问限定符来声明。参数属性可以方便地让我们在一个地方定义并初始化类成员。

class Car {
    constructor(public engine: string) {}
    drive() { }
}

抽象类

Typescript有抽象类的概念,它是供其他类继承的基类,不能直接被实例化。不同于接口,抽象类必须包含一些抽象方法,同时也可以包含非抽象的成员。抽象类中的抽象方法必须在派生类中实现。

abstract class Person {
    abstract speak(): void;
    walking(): void {
        console.log('walking');
    }
}

class Male extends Person {
    speak(): void {
        console.log('man wakling')
    }
}

接口

接口在面向对象设计中具有极其重要的作用,在Gof的23种设计模式中,基本上都可见到接口的身影。长期以来,接口模式一直是Javascript这类弱类型语言的软肋,Typescript接口的使用方式类似于Java。 在Typescript中接口有属性类型、函数类型、可索引类型、类类型这几种,在Angular的开发中主要使用类类型接口,我们使用interface关键字定义接口并用implements关键字实现接口。

interfance Animal {
    name: string;
    setName();
}

class Dog implements Animal {
    name: string;
    setName() {
        console.log(this.name)
    }
    constructor() { }
}
//接口更注重功能的设计,抽象类更注重结构内容的体现

模块

ES6中引入了模块的概念,在TypeScript中也支持模块的使用。使用import和export关键字来建立两个模块之间的联系。

##装饰器 装饰器(Decorators)是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上。装饰器有@符号紧接一个函数名称,如:@expression,expression求职后必须是一个函数,在函数执行的时候装饰器的声明方法会被执行。装饰器是用来给附着的主题进行装饰,添加额外的行为。(装饰器属于ES7规范)

在Typescript的源码中,官方提供了方法装饰器、类装饰器、参数装饰器、属性装饰器等几种每种装饰器类型传入的参数大不相同。这里我演示两种装饰器。例如:

function Component(component) {
    console.log('selector: ' + component.selector);
    console.log('template: ' + component.template);
    console.log('component init');
    return (target: any) => {
        console.log('component call');
        return target;
    }
}

function Directive() {
    console.log('directive init');
    return (target: any) => {
        console.log('directive call');
        return target;
    }
}

@Component({
    selector: 'person',
    template: 'person.html'
})
@Directive()
export class Person {}

let p = new Person();

C#的首席架构师以及Delphi和Turbo Pascal的创始人安德斯•海尔斯伯格参与了TypeScript的开发。Typescript是ES6的超集。添加了可选的静态类型(注意并不是强类型)和基于类的面向对象编程。(如果对ES6熟悉那么可以只关注类、装饰器部分的内容。)

泛型

泛型是参数化的类型,一般用来限制集合的内容。例如:

class MinHeap<T> {
    list: T[] = [];
    
    add(element: T): void {
        //这里进行大小比较,并将最小值放在数组头部,功能代码省略。
    }
    min(): T {
        return this.list.length ? this.list[0] : null
    }
}

let heap = new MinHeap<number>();
heap.add(3);
heap.add(5);
console.log(heap.min())