TypeScript学习日志

==前言==

什么是TypeScript

TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,兼容JavaScript,可以载入JS代码然后运行。它与JavaScript相比进步的地方包括:加入注释,让编译器理解所支持的对象和函数,编译器会移除注释,不会增加开销。 而JavaScript只是一个脚本语言,并非设计用于开发大型 Web 应用,在ES2015之前,JavaScript 没有提供类和模块的概念,而TypeScript扩展实现了这些特性。

不要将TypeScript看作是一门新的语言,它只是为了提升JavaScript代码质量的一个工具,最终TypeScript仍然要编译成JavaScript。

TypeScript 最大的特点就是类型化,因此才叫做TypeScript。比起弱类型的JavaScript,类型化的TypeScript显得更加容易维护。

要在应用中使用 TypeScript 必须先编译,编译的结果是生成 js 文件,你可通过 TypeScript 编译器 tsc(需先npm安装tsc) 命令来完成这个过程。如要编译test.ts(TypeScript文件的扩展名为.ts),可用如下命令:

tsc test.ts 

编译完成后就会在当前目录生成名为test.js的文件。

类型注解

类型注解在TypeScript中是记录函数或变量约束的简便方法。在这个示例中,我们想要在调用greeter函数时传入一个字符串类型参数。我们可以尝试在调用greeter函数时变为传入一个数组:

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

var user = [0, 1, 2];

document.body.innerHTML = greeter(user);

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

greeter.ts(7,26): Supplied parameters do not match any signature of call target

注意,虽然有错误,但是仍然编译创建了greeter.js文件。即使你的代码中有错误,你仍旧可以使用TypeScript。但是在这种情况,TypeScript会发出警告:你的代码可能不能按照你预想的那样运行。

==基本数据类型==

TypeScript作为强类型编程语言 提供了若干种基本数据类型

布尔值

let flag: boolean = true;

数字

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

支持ES2015中的二进制和八进制

字符串

支持模板字符串

let str: string = "adam";
let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.

I'll be ${ age + 1 } years old next month.`;

数组

Typescript中可以有两种方式定义数组

let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

++敲黑板++

以下是TypeScript额外提供的数据类型

元组Tuple

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error

当访问一个越界的元素,会使用联合类型替代:

x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型

console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString

x[6] = true; // Error, 布尔不是(string | number)类型

枚举

enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。

enum Color {Red, Green, Blue}; //1 2 3
let c: Color = Color.Green;

默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1开始编号:

enum Color {Red = 1, Green, Blue};
let c: Color = Color.Green;

也可以完全采用全部手动赋值

enum Color {Red = 1, Green = 2, Blue = 4};
let c: Color = Color.Green;

枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:

enum Color {Red = 1, Green, Blue};
let colorName: string = Color[2];

alert(colorName);  //Green

任意值 any

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

定义为any后,将失去语法感知的功能,就相当于写JavaScript 一样。

值得一提的是,any可以配合数组来使用,代码如下:

var list: any[] = [1, true, "free"];
list[1] = 100; //更改list[1]的值

空值

这个类型仅能在函数中使用,可以将函数的返回类型指定为 void,表示该函数不返回任何值。

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void:

function warnUser(): void {
    alert("This is my warning message");
}

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null

Never -->不存在的

never类型表示的是那些永不存在的值的类型。never 可以是永远不返回的函数的返回值类型

// 推断的返回值类型为 number
function move1(direction: "up" | "down") {
    switch (direction) {
        case "up":
            return 1;
        case "down":
            return -1;
    }
    return error("永远不应该到这里");
}

类型断言

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

//1
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
//2
let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

==函数的定义与调用==

在TypeScript中定义函数的语法为:

function function_name(arg:number,arg1:number,....):return_type{
    code 函数要执行的代码;
    return data;
}

实例

function add(x: number, y: number): number {  //定义返回值为number类型的函数
    return x+y;
}
add(5,6); //调用函数

可选参数

在参数名后面,冒号前面添加一个问号,则表明该参数是可选的。如下代码:

function buildName(firstName: string, lastName?: string) { //lastName为可选参数
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
var result1 = buildName("Bob");  //正确调用 Bob
var result2 = buildName("Bob", "Adams"); //正确调用 Bob Adams

默认参数

在参数名后直接给定一个值,如果这个值没有被传入,那么将会被赋值为默认值。如下代码:

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}
 
var result1 = buildName("Bob");  //没有传入第二个参数,则被赋值为默认的smith,结果为:Bob Smith
var result2 = buildName("Bob", "Adams");  //结果为:Bob Adams

PS:可选参数和默认参数必须在参数列表的最后。

==类的结构和声明==

ES2015之前,JavaScript语言基于函数和原型链继承机制的方式构建可重用的组件。稍显笨拙且难以理解,所以TypeScript在ES2015之前就提供了基于类的设计方式。

创建一个TypeScript类时,必须使用关键字class进行声明,该关键字后紧跟类的名称,之后用大括号将类体进行封装,类的基本声明格式如下。

class 类名{
    //类体
    name:string;  //定义类的属性
 
    fun(){ //定义类的方法
        //定义该方法所要实现的功能
    }
}

类的静态属性

TypeScript可以使用“static” 关键字标注类的成员。如下代码:

class calc{
    static count=10;
    add(data1:number):number{
        var sum=calc.count+data1;
        return sum;
    }
}
var test=new calc();
document.write(test.add(20));

类成员的静态属性我们可以直接调用,调用方式为如上例的count的调用方式:calc.count。而不能用this.count在类的内部使用。

构造函数

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。而TypeScript的构造函数用关键字constructor来实现。可以通过this关键字来访问当前类体中的属性和方法。如下代码:

class student{  //定义student类
    name:string;  //定义类的属性
    constructor(myname:string){ //定义构造函数
        this.name=myname;
    }
    study(){ //定义类的方法
        //定义该方法所要实现的功能
    }
}

类的实例化

一般情况下,创建一个类后并不能直接的对属性和方法进行引用,必须对类进行实例化,即创建一个对象。TypeScript中用new 关键字创建对象。实例化后通过“.”来访问属性和方法。实例代码如下:

class student{  //定义student类
    name:string;  //定义类的属性
    constructor(myname:string){ //定义带参数的构造函数
        this.name=myname;
    }
    study(){ //定义类的方法
        document.write("<h1> My name is "+this.name+".</h1>");
    }
    write():string{
        return "write name:"+this.name;
    }
}
var s1=new student("Jim");
document.write("<h1>"+s1.name+"</h1>"); //获取name属性
s1.study();   // 调用study方法  
document.write("<h1>"+s1.write()+"</h1>");

继承

TypeScript中用关键字extends指明继承关系。例如,已经定义了类A,如果让类B继承A,我们把A叫做基类,B叫子类。可以用下面的方式定义类B。

class B extends A {
    // 类B中的成员
}

如果我们要在子类中调用基类中的属性与方法就要使用super关键字。如下代码:

class Animal {  //定义基类
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number) {
        document.write(this.name + " moved " + meters + "m.");
    }
}
 
class Snake extends Animal { //继承基类
    constructor(name: string) { 
        super(name); //调用基本类的构造函数
    }
    move() { //重写了基类的方法
        document.write("Slithering...<br>");
        super.move(5); //调用基类的方法
    }
}
var sam = new Snake("Python"); //声明Snake类
sam.move();

在TypeScript中我们采用“extends”关键字来表示类的继承关系。在这里你可以看见“Snake”继承“Animal”的子类实现。在实例中也展示如何去重写父类的方法,在这里“Snake”创建了一个“move”方法来重写父类“Animal”的“move”方法,并通过“super”关键字来调用父类的方法。

==接口==

在TypeScript中,接口是用作约束作用的,在编译成JavaScript的时候,所有的接口都会被擦除掉,因为 JavaScript中并没有接口这一概念。TypeScript中接口是用关键字interface进行声明

下面的代码中,printLabel函数要求传入一个包含一个label的字符串属性。而接口LabelledValue描述了printLabel的所要求的类型对象。它依然代表包含一个label的字符串属性。

interface LabelledValue {  //定义接口
    label: string;
}
function printLabel(labelledObj: LabelledValue) { //定义函数printLabel,其参数类型为接口类型
    document.write(labelledObj.label);
}
var myObj = {size: 10, label: "Size 10 Object"}; //定义含有接口中属性的对象
printLabel(myObj); //调用函数

可选属性

有时不是所有定义在interface中的属性都是必须的,typescript中便为我们提供了可选属性。带有可选属性的interface定义和c#语言很相似,以?紧跟变量名后边表示。如下代码:

interface SquareConfig { //定义了两个可选属性
    color?: string;  
    width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {//定义函数
    var newSquare = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}
 
var mySquare = createSquare({color: "black"}); //调用函数,
document.write(mySquare.color);   //结果为: black

对比起完全不定义,定义可选属性主要是:如果存在属性,能约束类型,而这也是十分关键的。

接口约束方法类型

下面的代码中,我们定义了一个接口,接口内约束了一个方法的签名,这个方法有两个字符串参数,返回布尔值。在第二段代码中我们声明了这个接口的实现。编译器仅仅检查类型是否正确(参数类型、返回值类型),因此参数的名字我们可以换成别的。

interface SearchFunc {  
    (source: string, subString: string): boolean; //定义一个匿名方法
}
 
var mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {  //实现接口
    var result = source.search(subString);  //调用系统方法search查找字符串的位置
    if (result == -1) {
        return false;
    }
    else {
        return true;
    }
}

接口约束数组类型

在前面一节中我们学习了接口定义方法类型,这一节我们来学习接口定义数组类型。在数组类型中有一个“index”类型其描述数组下标的类型,以及返回值类型描述每项的类型。如下:

interface StringArray { //定义数组接口
    [index: number]: string;  //每个数组元素的类型
}
 
var myArray: StringArray; 
myArray = ["Bob", "Fred"];

接口约束Class

这里接口用来强制其实现类符合它的约束。通过类实现接口要用implements关键字。如下代码:

interface IPrint{
    print();
}
 
class A implements IPrint  { //实现接口
    print(){  //实现接口中的方法
        document.write("实现接口");
    }
}
 
var B=new A();  
B.print();

接口的继承

和类一样,接口也能继承其他的接口。这相当于复制接口的所有成员。接口也是用关键字“extends”来继承。

interface Shape {     //定义接口Shape
    color: string;
}
 
interface Square extends Shape {  //继承接口Shape
    sideLength: number;
}

一个interface可以同时继承多个interface,实现多个接口成员的合并。用逗号隔开要继承的接口。

interface Shape {
    color: string;
}
 
interface PenStroke {
    penWidth: number;
}
 
interface Square extends Shape, PenStroke {
    sideLength: number;
}

需要注意的是,尽管支持继承多个接口,但是如果继承的接口中,定义的同名属性的类型不同的话,是不能编译通过的。如下代码:

interface Shape {
    color: string;
    test: number;
}
 
interface PenStroke extends Shape{
    penWidth: number;
    test: string;
}

==模块==

模块的声明

假设我们在前面的知识基础上写了一个数据验证程序,代码如下

interface StringValidator {  //定义验证接口
    isAcceptable(s: string): boolean;
}
 
var lettersRegexp = /^[A-Za-z]+$/;
var numberRegexp = /^[0-9]+$/;
 
class LettersOnlyValidator implements StringValidator { //实现接口
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}
 
class ZipCodeValidator implements StringValidator {   //实现接口
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
 
var strings = ['Hello', '98052', '101'];
var validators: { [s: string]: StringValidator; } = {};
validators['ZIP code'] = new ZipCodeValidator();  //实例化类
validators['Letters only'] = new LettersOnlyValidator(); //实例化类
for(var i=0;i<strings.length;i++){
    for (var name in validators) {
        document.write('"' + strings[i] + '" ' + (validators[name].isAcceptable(strings[i]) ? ' matches ' : ' does not match ') + name+"<br>"); //调用类的方法
    }
}

上面这段代码可以正常运行,但是有几个问题。一个是没法复用,验证的封装和验证过程在同一个文件,验证的封装已经是可以复用的。另一个是接口和两个实现的类都直接挂接在全局变量上,假如数量一多的话,将会影响整个全局变量。

而TypeScritp中模块的出现给我们解决了这一问题。使用 module 关键字来定义模块,并在末尾加花括号即可用; 用export 关键字使接口、类等成员对模块外可见。

module Validation {   //定义模块
    export interface StringValidator {  //声明接口对外部可以使用
        isAcceptable(s: string): boolean;
    }
 
    var lettersRegexp = /^[A-Za-z]+$/;
    var numberRegexp = /^[0-9]+$/;
 
    export class LettersOnlyValidator implements StringValidator {  //声明类对外部可用
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
 
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

在模块声明完成以后,我们就可以调用这个模块了,调用模块中的接口、类、方法等。调用方法简单,就是用模块名后面跟一个点来调用类、接口、方法等。如下代码:

module Validation {  //定义模块
    export interface StringValidator {  //声明接口对外部可以使用
        isAcceptable(s: string): boolean;
    }
 
    var lettersRegexp = /^[A-Za-z]+$/;
    var numberRegexp = /^[0-9]+$/;
 
    export class LettersOnlyValidator implements StringValidator { //声明类对外部可用
        isAcceptable(s: string) {
        return lettersRegexp.test(s);
        }
    }
 
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}
 
var strings = ['Hello', '98052', '101'];
var validators: { [s: string]: Validation.StringValidator; } = {};
validators['ZIP code'] = new Validation.ZipCodeValidator();  //使用模块中的类
validators['Letters only'] = new Validation.LettersOnlyValidator();
// 显示匹配结果
for(var i=0;i<strings.length;i++){
    for (var name in validators) {
        document.write('"' + strings[i] + '" ' + (validators[name].isAcceptable(strings[i]) ? ' matches ' : ' does not match ') + name+"<br>"); // 使用方法
    }
}

模块代码分割

随着我们项目的扩展,为了更好地维护项目,我们会将特定功能放到一个文件里,然后加载多个文件实现我们想需要的功能。现在我们先将上面的代码分割到多个文件里。

//validation.ts
module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}
//lettersOnlyValidator.ts
/// <reference path="Validation.ts" />
module Validation {
    var lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}
//zipCodeValidator.ts
/// <reference path="Validation.ts" />
module Validation {
    var numberRegexp = /^[0-9]+$/;
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}
//test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
 
var strings = ['Hello', '98052', '101'];
var validators: { [s: string]: Validation.StringValidator; } = {};
validators['ZIP code'] = new Validation.ZipCodeValidator();
validators['Letters only'] = new Validation.LettersOnlyValidator();
for(var i=0;i<strings.length;i++){
    for (var name in validators) {
        document.write('"' + strings[i] + '" ' + (validators[name].isAcceptable(strings[i]) ? ' matches ' : ' does not match ') + name+"<br>"); //调用类的方法
    }
}

在项目中新建好以上四个文件,然后我们编译项目,如果我们代码编写没错的话,是能够编译通过的。另外,我们可以见到后面三个文件开头有类似于 C# 的文档注释,这是告诉 TypeScript 编译器该文件依赖于哪些文件,假如依赖的文件不存在的话,编译就会不通过。当然我们不写也是可以的,只不过编译器在编译时不会帮我们检查,一般来说,还是建议写上。 另外,在引用编译生成的 JavaScript 文件时,我们需要注意好顺序。以上面的代码为例,我们在 Html 代码中已经这么引用。

<script src="Validation.js" type="text/javascript"/>
<script src="LettersOnlyValidator.js" type="text/javascript"/>
<script src="ZipCodeValidator.js" type="text/javascript"/>
<script src="Test.js" type="text/javascript"/>

最后再说两句:

  • angular1.0项目开发总结
  • vue cli小型实战项目总结
  • 结合音乐播放器讲解MDUI

转载于:https://my.oschina.net/AdamCao/blog/862544

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值