简介

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。

官网:TypeScript

中文:TypeScript

Hello World

npm install typescript -g
tsc --version

新建一个helloworld.ts填写:

var a:string = "Hello World";
console.log(a);

然后打开终端输入:

tsc helloworld.ts
node helloworld.js

tsc xxx.ds执行会编译原生JS文件,大概这样一个过程就这样吧。

变量

TypeScript数据类型:

  • Undefined未定义变量,表示变量没有被初始化;
  • Number数值类型;
  • String字符串类型;
  • Boolean布尔类型;
  • Enum枚举类型;
  • Any任意类型;
  • Void空类型
  • Array数组类型;
  • Tuple元组类型;
  • Null空类型;

里面有几种JavaScript都有故不在描述,所以就挑几个没见过的。

Enum 枚举类型:用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。

说简单就例如一周只有七天,分别是星期一,星期二....星期日。比如一个月30天等等。

enum DAY {
    MONDAY = "星期一";
    TUESDAY = "星期二";
    WEDNESDAY = "星期三";
    THURSDAY = "星期四";
    FRIDAY = "星期五";
    SATURDAY = "星期六";
    SUNDAY = "星期天";
}
console.log(DAY.THURSDAY);
//=> 星期四

Any类型:表示当前对象属性不确定,可以用Any表示。

var a:any = 10;
a = "Hello";
a = false;
console.log(a);

Tuple元组类型:是一种特殊的数组,元祖类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。例如定义一个String类型和Number的元组:

let x: [String, Number];
x = ["Hello", 100];
 
x = [100, "Hello"]; //错误的写法!

函数

将一个功能相似的需求封装到一个代码块,这就是一个函数,例如三个人,他们有不同的年龄,名字,性别,分别传入不同的名字,年龄,性别得到是三个完全不一样的人类。

function humanity(name:string,age:number,sex:string):string {
    return "他叫" + name + ",今年" + age + "岁了,是一个" + sex + "生";
}
console.log(humanity("王大",23,"男"));
//=>他叫王大,今年23岁了,是一个男生
  1. 声明(定义)函数必须加 function 关键字;
  2. 函数名与变量名一样,命名规则按照标识符规则;
  3. 函数参数可有可无,多个参数之间用逗号隔开;
  4. 每个参数参数由名字与类型组成,之间用分号隔开;
  5. 函数的返回值可有可无,没有时,返回类型为 void;
  6. 大括号中是函数体;

形参和实参

想上面的humanity("王大",23,"男")里面包含的函数指的是实参:调用函数时传递的具体值就是实参。

那么形参是什么?说通俗点就是可能不确定的参数,因为你不确定这个参数是否要用到。

TypeScript函数参数

在TypeScript语言中,函数的形参分为:可选形参、默认形参、剩余参数形参等。

可选参数:

定义形参的时候,可以定义一个可传可不传的参数。这种参数,在定义函数的时候通过?标注

例如现在我有个情况,我现在需要找一个人,找染头发的人类:

function searchHumanity1(age:number,hairColor?:string):string {
    let tip:string = '';
    tip = "找到" + age + "岁的人类"
    if( hairColor !== undefined ) {
        tip = tip + ",并且染了" + hairColor + "头发!"
    }
    return tip;
}
console.log(searchHumanity1(24,"黄"));
//=>找到24岁的人类,并且染了黄头发!
console.log(searchHumanity1(23));
//=>找到23岁的人类

有默认参数的函数:

当参数不传递的时候,会定义一个默认值,而不是undefined。根据上面的代码继续改:

function searchHumanity2(age:number,hairColor:string="黑"):string {
    let tip:string = '';
    tip = "找到" + age + "岁的人类"
    if( hairColor !== undefined ) {
        tip = tip + ",并且染了" + hairColor + "头发!"
    }
    return tip;
}
console.log(searchHumanity2(24));
//=>找到24岁的人类,并且染了黑头发!

有剩余参数函数:

当你编写一个函数,传递的参数个数不确定,例如我要找一个有特征的人类,但我不确定是什么,可能要找个大眼睛的人类,身高特高的人类等等这样一个情况:

function searchHumanity3(age:number,...feature:string[]):string {
    let tip:string = '';
    tip = "找到" + age + "岁"
    for(let i = 0; i < feature.length; i++) {
        tip = tip + feature[i];
        if(i < feature.length - 1) {
            tip = tip + "、"
        }
    }
    tip = tip + "的人类";
    return tip;
}
console.log(searchHumanity3(18, '大眼睛', '小嘴巴', '长得丑'));
//=>找到18岁大眼睛、小嘴巴、长得丑的人类
console.log(searchHumanity3(18));
//=>找到18岁的人类

函数定义

TypeScript函数定义有三种方法:函数声明法、函数表达式法、箭头函数。

函数声明法:

函数声明法创建函数是最常用的函数定义法,使用function关键字和函数名去定义一个函数:

function say(say:string):string {
    return say;
}

函数表达式:

函数表达式法是将一个函数赋值给一个变量,这个变量名就是函数名。通过变量名就可以调用函数了。这种方式定义的函数,必须在定义之后,调用函数。下面例子中等号右边的函数没有函数名,称为匿名函数:

let numberTol = function(num1:number,num2:number):number {
    return num1 + num2;
}
console.log(numberTol(1,2))
//=>3

箭头函数:

箭头函数是 ES6 中新增的函数定义的新方式:

let numberTol = function(num1:number,num2:number):number {
    return num1 + num2;
}
console.log(numberTol(1,2))

变量作用域

在高级编程中都有变量作用域这个概念,基本分为全局变量局部变量

  • 局部变量:函数体内定义的变量就是局部变量。
  • 全局变量:函数体外定义的变量就是全局变量。
var string1:string = 'Hello';
function echo():void {
    var string1:string;
    console.log('内部1:',string1);
    string1 = 'World';
    console.log('内部2:',string1);
}
echo();
//=>内部1: undefined
//=>内部2: World
//=>外部: Hello

产生这个结果的原因就是变量提升,也就是当内部声明了和全局的变量同名时,就会出现变量提升的效果,声明语句会提升到函数的第一句。

所以在ES6有个let关键词:使用let关键字的变量就是一个块级作用域变量。

function echo():void {
    let string1:string = 'Hello';
    {
        let string2:string = 'World';
        console.log('内部1:',string2);
    }
    console.log('内部2:',string1);
    console.log('内部3:',string2);
}
echo();
//=>内部1: World
//=>内部2: Hello
//=>内部3: World

但在上面的内部3输出是报错的,但编译成功了,这和ES5遗留问题有关,本文不再描述。

引用类型

TypeScript中的数据分为值类型和引用类型。值类型类似变量等,那么引用类型是什么?

let people = {
    name: 'Jaxson',
    age: '23',
    sex: '男',
    saySometing: function() {
        console.log('永远年轻,永远热泪盈眶!');
    }
}
console.log(people.name);
people.saySometing();
//=>Jaxson
//=>永远年轻,永远热泪盈眶!

从上面看到引用类型是一种复合的数据类型,引用类型中封装了很多对属性,每一对属性都有属性名和属性值。属性名是字符串,属性值是任意类型的数据。可以通过变量名和属性名获取属性的值或者调用属性的方法。

在TypeScript中也提供了一些引用类型,例如:Array(数组)、String(字符串)、Date(日期对象)、RegExp(正则表达式)等。

数组的初始化:

声明数组跟声明一个普通变量是一样的,都是通过var let 关键字实现的,只不过数组的类型说明符比较复杂而已。

字面量赋值法:

let arr1:number[] //声明一个数值类型的数组
let arr2:Array<string> ////声明一个字符串类型的数组
 
//字面量赋值法:直接使用“[ ]”对数组进行赋值。
let arr3:number[] = [1,2,3,4,5] //数值类型赋值
let arr4:Array<string> = ['Hello','World'] //字符串数组赋值
let arr5:Array<boolean> = [true,false] //布尔值数组类型

在TypeScript中指定数据类型的数组只能存储同一类型的数组元素。那么你需要不一样类型的请使用元祖类型。

构造函数赋值法:

在 TypeScript 中使用 Array 这个引用类型来表示数组的,那么每一个数组都是 Array 类型的实例:

let array1:number[] = new Array();
let array2:number[] = new Array(1,2,3,4);
let array3:Array<string> = new Array('Hello','World');
let array3:Array<boolean> = new Array(true,false);

字符串的俩种类型:

  • 基本类型字符串:由单引号或者双引号括起来的一串字符串。
  • 引用类型字符串:用new 实例化的 String类型。
let hello:string = 'Hello';
let world:String = new String('World');
console.log(hello);
console.log(world);
//=>Hello
//=>[String: 'World']

从输出情况来看,这俩者是不一样的,但这实际上开发过程中,这俩者开发效果是没啥不同的,都一样一个效果。基本类型的字符串可以直接使用引用类型的属性和方法。

同样类似new String()还有new Date()new RegExp()等等,大部分写法和JavaScript一样,不再描述。

面向对象编程

类的声明和使用:

TypeScript最大的特点就是有个类的支持,所以有类的特性基本上都支持面向对象编程。

我记得Java有句话是:类是对象具体事务的一个抽象,对象是类的具体表现。应该很好诠释这个定义。

class People1{
    name:string;
    age:number;
    sex:string;
    constructor(name:string,age:number,sex:string) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    say() {
        console.log('Hello World')
    }
}
 
let jaxson:People1 = new People1('王大',23,'男');
console.log(jaxson);
jaxson.say();
//=>People { name: '王大', age: 23, sex: '男' }
//=>Hello World

访问修饰符:

在高级编程基本都有访问修饰符这块东西,同样在TypeScript也有这个:类中属性的访问可以用访问修饰符来进行限制。访问修饰符分为:publicprotectedprivate

  • public:公有修饰符,可以在类内或者类外使用public修饰的属性或者行为,默认修饰符。
  • protected:受保护的修饰符,可以本类和子类中使用protected修饰的属性和行为。
  • private : 私有修饰符,只可以在类内使用private修饰的属性和行为。
class People2{
    public name:string;
    protected age:number;
    private sex:string;
    public constructor(name:string,age:number,sex:string) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    public say1() {
        console.log('Hello World')
    }
 
    protected say2() {
        console.log('你好')
    }
}
 
let chuizi:People2 = new People2('王大锤',25,'女');
console.log(chuizi.name); //=>王大锤
console.log(chuizi.age); //提示报错:属性“age”受保护,只能在类“People2”及其子类中访问。
console.log(chuizi.sex); //提示报错:属性“sex”为私有属性,只能在类“People2”中访问。
chuizi.say1(); //=>Hello World
chuizi.say2(); //提示报错:属性“say2”受保护,只能在类“People2”及其子类中访问。

TypeScript还有个属性叫readonly:使用readonly修饰符将属性设置为只读,只读属性必须在生命时或者构造函数里被初始化。

class People3{
    public readonly name:string = '小明';
}
 
let xiaoming:People3 = new People3();
xiaoming.name = '小红'; //无法分配到“name”,因为它是常数或只读属性。

继承和重写

类的继承:

允许我们创建一个类(子类),从已有的类(父类)上继承所有的属性和方法,子类可以新建父类中没有的属性和方法。

例如新建一个父类,这个类是人类的抽象:

class PeopleAll {
    public name:string;
    public age:number;
    public sex:string;
    public constructor(name:string,age:number,sex:string) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    public say() {
        console.log('是人都会说话!')
    }
}
 
let renlei:PeopleAll = new PeopleAll('王大春', 26, '男');
renlei.say();

上面就是一个人类的父类抽象模板,现在这里有不同的人类,他们都有不同的特点。例如会烧饭,会打架什么,所以需要继承人类的模板:

class WangDaChui extends PeopleAll {
    public skill:string = '很帅';
 
    public capability() {
        console.log('我会打架!')
    }
}
 
let wangdachui = new WangDaChui('王大锤', 23, '男');
wangdachui.say();
wangdachui.capability();

类的重写:

例如有个王大锤的人类,他继承父类人类的基本特征:姓名、年龄、性别。还有自己的特征:很帅和会打架。

但现在发现王大锤不仅仅会说话,他还会闭嘴这个行为,那么需要重写父类的say()行为:

class WangDaChui extends PeopleAll {
    public skill:string = '很帅';
 
    public say() {
        //super.say();
        console.log('我不仅会说话,还会闭嘴!')
    }
 
    public capability() {
        console.log('我会打架!')
    }
}
 
let wangdachui = new WangDaChui('王大锤', 23, '男');
wangdachui.say();
wangdachui.capability();

super.say();是调用了父类的方法,实现了属性的增加。

接口:

在通常情况下,接口是用来定义一些规范,使用这些接口,就必须实现按照接口中的规范来走。

在面向对象的语言中,术语interface经常被用来定义一个不包含数据和逻辑代码但是用来签名定义了行为的抽象类型。

例如现在定义个接口,来规范一个人类:

class MalePeople {
    sex:string;
    hobby:string;
}
 
let male1:MalePeople = {
    sex: '男',
    hobby: '撸代码'
}
console.log(male1);
//=>{ sex: '男', hobby: '撸代码' }

上面声明一个接口,来定义这个人类的性别和爱好。

可选参数:

作为人类有些人会某些事,这些东西可以作为可选选项,只需要传入部分参数:

class MalePeople1 {
    sex:string;
    hobby:string;
    cooking?:boolean;
}
 
let male1:MalePeople1 = {
    sex: '男',
    hobby: '撸代码',
    cooking: true
}
console.log(male1);
//=>{ sex: '男', hobby: '撸代码', cooking: true }

命名空间

命名空间的关键词是:namespace,使用方法和PHP命名空间一样,作用就是被用于组织有些具有内在联系的特性和对象:

namespace people1 {
    export class renlei {
        public name:string = '王大锤';
        say() {
            console.log('你好!')
        }
    }
}
 
namespace people2 {
    export class renlei {
        public name:string = '小红';
        say() {
            console.log('Hello')
        }
    }
}
 
let ren1:people1.renlei = new people1.renlei();
let ren2:people2.renlei = new people2.renlei();
console.log(ren1);
console.log(ren2);
//=>renlei { name: '王大锤' }
//=>renlei { name: '小红' }

源码存放:https://github.com/JaxsonWang/TypeScript-Study