前端入门25-福音 TypeScript

声明

本篇内容摘抄自如下来源:javascript

只梳理其中部分知识点,更多更详细内容参考官网。html

正文-TypeScript

今天来说讲有 Java 基础转 JavaScript 的福音:TypeScriptjava

为何学习 TypeScript

若是学习 JavaScript 以前已经有了 Java 的基础,那么学习过程当中确定会有不少不习惯的地方,由于 JavaScript 无论是在语法上面、仍是编程思想上与 Java 这类语言都有一些差别。node

下面就大概来看几个方面的差别:es6

变量声明

JavaScript 是弱语言,声明变量时无需指明变量的数据类型,运行期间会自动推断,因此声明方式很简单:web

var a = 1;
var wx = "dasu_Android"

Java 是强类型语言,声明变量时必须明确指出变量数据类型:typescript

int a = 1;
String wx = "dasu_Android";

弱类型语言虽然比较灵活,但也很容易出问题,并且须要一些额外的处理工做,好比函数期待接收数组类型的参数,但调用时却传入了字符串类型,此时 js 引擎并不会报错,对于它来讲,这是合理的行为,但从程序、从功能角度来看,也许就不会按照预期的执行,因此一般须要在函数内部进行一些额外处理,若是没有额外处理,那么因为这种参数类型致使的问题也很难排查。npm

变量做用域

JavaScript 的变量在 ES5 只有全局做用域和函数内做用域,ES6 新增了块级做用域。编程

Java 的变量分:类变量和实例变量,属于类的变量若是是公开权限,那么全部地方都容许访问,属于实例的变量,分红员变量和局部变量,成员变量在实例内部全部地方均可以访问,在实例外部若是是公开权限,可经过对象来访问,局部变量只具备块级做用域。json

  • 变量被覆盖问题

由于 JavaScript 在 ES5 时并无块级做用域,有些场景下会致使变量被覆盖的状况,因为这种状况形成的问题也很难排查,好比:

function aaa() {
    var i = -1;
    for (var i = 0; i < 1; i++) {
        for (var i = 1; i < 2; i++) {

        }
        console.log(i);
    }
    console.log(i);
}

在 Java 中,两次 i 的输出应该 0, -1,由于三个地方的 i 变量并非同一个,块级做用域内又生成一个新的局部 i 变量,但在 JavaScript 里,ES5 没有块级做用域,函数内三个 i 都是同一个变量,程序输出的是:2,3。此时就发送变量被覆盖的状况了,。

  • 拼写错误问题

并且,JavaScript 的全局变量会被做为全局对象的属性存在,而在 JavaScript 里对象的属性是容许动态添加的,这就会致使一个问题:当使用某变量,但拼写错误时,js 引擎并不会报错,对它来讲,会认为新增了一个全局对象的属性;但从程序,从功能角度来看,经常就会致使预期外的行为,而这类问题也很难排查,好比:

var main = "type-script";
function modify(pre) {
    mian = `${pre}-script`;
}
modify(123);

在 Java 里会找不到 mian 变量报错,但在 JavaScript 里 mian 会被当作全局对象的属性来处理。

  • 全局变量冲突问题

并且,JavaScript 的变量容许重复申请,这样一来,全局变量一旦多了,很容易形成变量冲突问题,这类问题即便在运行期间也很难被发现和排查,好比:

//a.js
var a = 1;

//b.js
var a = "js";

在不一样文件中,若是全局变量命名同样,会致使变量冲突,但浏览器不会有任何报错行为,由于对它来讲,这是正常的行为,但对于程序来讲,功能可能就会出现预期外的行为。

继承

JavaScript 是基于原型的继承,原型本质上也是对象,因此 JavaScript 中对象是从对象上继承的,同时对象也是由对象建立的,一切都是对象。

Java 中有 class 机制,对象的抽象模板概念,用于描述对象的属性和行为以及继承结构,而对象是从类实例化建立出来的。

正是由于 JavaScript 中并无 class 机制,因此有 Java 基础的可能会比较难理解 JavaScript 中的继承、实例化对象等原理。

那么在面向对象的编程中,自定义了某个对象,并赋予它必定的属性和行为,这样的描述在 Java 里很容易实现,但在 JavaScript 里却须要经过定义构造函数,对构造函数的 prototype 操做等处理,语义不明确,不怎么好理解,好比定义 Dog 对象:

function Dog() {}
Book.prototype.eat = function () {
    //...
} 
Book.prototype.name = "dog";

对于习惯了 Java 的面向对象编程,在 JavaScript 里自定义一个 Dog 对象的写法可能会很不习惯。

Class 机制

JavaScript 虽然在 ES6 中加入了 class 写法,但本质上只是语法糖,并且从使用上,仍旧与 Java 的 class 机制有些区别,好比:

class Animal {
    constructor(theName) {
        this.name = theName;
        this.ll = 23;
    }
    move(distanceInMeters = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

以上是 JavaScript 中 ES6 自定义某个类的用法,与 Java 的写法有以下区别:

  • 类的属性只能在构造函数内声明和初始化,没法像 Java 同样在构造函数外面先声明成员变量的存在;
  • 没法定义静态变量或静态方法,即没有 static 语法;

权限控制

JavaScript 里没有 public 这些权限修饰符,对于对象的属性,只能经过控制它的可配置性、可写性、可枚举性来达到一些限制效果,对于对象,可经过控制对象的可扩展性来限制。

Java 里有 package 权限、publick 权限、protection 权限、private 权限之分,权限修饰符可修饰类、变量、方法,不一样权限修饰符可让被修饰的具备不同的权限限制。

在 JavaScript 若是要实现对外部隐藏内部实现细节,大多时候,只能利用闭包来实现。

抽象类

JavaScript 虽然在 ES6 中引入了 class 的写法,但本质上只是语法糖,并无相似 Java 中抽象类、抽象方法的机制存在,即便要模拟,也只能是定义一些抛异常的方法来模拟抽象方法,子类不实现的话,那么在运行期间就会抛异常,好比:

//不容许使用该构造函数建立对象,来模拟抽象类
function AbstractClass() {
    throw new Error("u can't instantiate abstract class");
}
//没有实现的抽象方法,经过抛异常来模拟
function abstractMethod() {
    throw new Error("abstract method,u should implement it");
}
//定义抽象方法,子类继承以后,若是不本身实现,直接使用会抛异常
AbstractClass.prototype.onMearsure = abstractMethod;
AbstractClass.prototype.onLayout = abstractMethod;

相比于 Java 的抽象类的机制,在编译期间就能够报错的行为,JavaScript 的运行期抛异常行为效果可能无法强制让全部开发者都能正确实现抽象方法。

对象标识

JavaScript 因为没有 class 机制,又是基于原型的继承,运行期间原型还可动态变化,致使了在 JavaScript 里没有一种完美的方式能够用来获取对象的标识,以达到区分不一样对象的目的。

Java 中的对象都是从类实例化建立出来的,所以经过 instanceof 便可判断不一样对象所属类别是否一致。

在 JavaScript 中,只能根据不一样使用场景,选择 typeof,instanceof,isPrototypeOf(),对象的类属性,对象的构造函数名等方式来区别不一样对象所属类别。

鸭式辩型

正是因为 JavaScript 里没有 class 机制,没有哪一种方式能够完美适用全部须要区分对象的场景,所以在 JavaScript 中有一种编程理念:鸭式辩型(只要会游泳且嘎嘎叫的鸟,也能够认为它是鸭子)

意思就是说,编程中不要从判断对象是不是预期的类别角度出发,而是从判断对象是否具备预期的属性角度出发。

小结

因此,对于若是有 Java 基础的,JavaScript 学习过程可能会有些不习惯,那么若是是 TypeScript 的话,能够说是个福利,由于 TypeScript 不少语法和编程思想上都跟 Java 很相似,很容易就理解。

那么,来认识下,TypeScript 是什么?

TypeScript 是 JavaScript 的超集,超集是什么意思,就是说,JavaScript 程序能够不加修改就运行在 TypeScript 的环境中,TypeScript 在语法上是基于 JavaScript 进行扩展的。

那么,TypeScript 在 JavaScript 语法基础上作了哪些扩展呢?其实就是加入了各类约束性的语法,好比加入了相似强类型语言的语法。

好比说,声明变量时,须要指定变量的数据类型的约束,以此来减小类型错误致使的问题。

let wx:string = "dasu_Android";

其实,本质上是由于 JavaScript 是解释型语言,由于没有编译阶段,不少问题只能是运行期才可能被发现,而运行期暴露的问题也不必定能够很好的排查出来。

而 TypeScript 语法编写的 ts 文件代码,浏览器并不认识,因此须要通过一个编译阶段,编译成 js 文件,那么 TypeScript 就提供了一个编译过程,加上它语法上的支持,在编译期间编译器就能够帮助开发者找出一些可能出错的地方。

举个例子:

var main = "type-script";
function modify(pre) {
    mian = `${pre}-script`;
}
modify(123);

这个例子中,定义了一个全局变量和一个函数,函数本意是接收一个字符串类型的值,而后修改这个全局变量的值,但开发者可能因为粗心,将全局变量的变量名拼写错误了,并且调用方法时并无传入字符串类型,而是数字类型。

若是是在 JavaScript 中,这段代码运行期间并不会报错,也不会致使程序异常,js 解释器会认为它是合理的,它会认为这个函数是用来增长全局对象的 mian 属性,同时函数参数它也不知道开发者但愿使用的是什么类型,它全部类型都接受。

因为程序并无出现异常,即便运行期间,开发者也很难发现这个拼写错误的问题,相反,程序因为拼写错误而没有执行预期的功能时,反而会让开发者花费不少时间来排查缘由。

但这段代码若是是用 TypeScript 来写:

这些基础的语法错误,编译器甚至不用进入编译阶段,在开发者刚写完这些代码就能给出错误提示。并且,一些潜在的可能形成错误的代码,在编译阶段也会给出错误提示。

虽然 TypeScript 语法上支持了不少相似于 Java 语言的特性,好比强类型约束等,但 JavaScript 本质上并不支持,能够看看上面那段代码最后编译成的 js 代码:

var main = "type-script";
function modify(pre) {
    mian = `${pre}-script`;
}
modify(123);

发现没有,编译后的代码其实也就是上述举例的 js 代码段,也就是说,你用 JavaScript 写和用 TypeScript 写,最后的代码都是同样的,区别在于,TypeScript 它有一个编译阶段,借助编译器能够在编译期就能发现可能的语法错误,不用等到运行期。

WebStrom 配置

将 TypeScript 编写的 ts 文件编译成 js 文件有两种途径,一是借助命令,二是借助开发工具。

若是电脑已经有安装了 node.js 环境,那么能够直接执行下述命令:

npm install -g typescript

而后打开终端,在命令行执行:

tsc xxx.ts

tsc 命令就能够将 ts 文件编译输出 js 文件了。

我选择的开发工具是 WebStrom,这个开发工具自己就是支持 TypeScript 的了,若是你有尝试过查看 ES五、ES6 相关 api,你可能会发现:

.d.ts 文件就是用 TypeScript 编写的,因此若是你熟悉 TypeScript 的语法,这些代码就能很清楚了,.d.ts 是一份声明文件,做用相似于 C++ 中的 .h 文件。

在 WebStrom 中右键 -> 新建文件中,能够选择建立 TypeScript 的文件,能够设置 FileWatcher 来自动编译,也能够将项目初始化成 node.js 项目,利用 package.json 里的 scripts 脚本命令来手动触发编译。

我选择的是后者,若是你对 package.json 或 FileWatcher 配置不熟悉,能够参考以前模块化那篇最后对这些配置的介绍

而编译器在编译过程,相似于 Android 里的 Gradle,能够设置不少配置项,进行不一样的编译,而 TypeScript 编译过程对应的配置文件是 tsconfig.json

tsconfig.json

TypeScript 中文网 里对于这份配置文件的描述很清楚了,这里摘抄部份内容:

  • 不带任何输入文件的状况下调用 tsc,编译器会从当前目录开始去查找 tsconfig.json 文件,逐级向上搜索父目录。
  • 不带任何输入文件的状况下调用 tsc,且使用命令行参数 --project(或 -p)指定一个包含 tsconfig.json 文件的目录。
  • 当命令行上指定了输入文件时,tsconfig.json 文件会被忽略。

示例:

{
  "compilerOptions": {
    "module": "commonjs",     //编译输出的 js 以哪一种模块规范实现,有 commonjs,amd,umd,es2015等等
    "target": "es5",          //编译输出的 js 以哪一种 js 标准实现,有 es3,es5,es6,es2015,es2016,es2017,es2018等
    "sourceMap": false,       //编译时是否生成对应的 source map 文件
    "removeComments": false,  //编译输出的 js 文件删掉注释
    "outDir": "./js/dist"     //编译输出的 js 路径
  },
  "exclude": [               //编译时排除哪些文件
    "node_modules"
  ]
}

语法

最后来看看一些基础语法,你会发现,若是你有 Java 基础,这些是多么的熟悉,用 TypeScript 来编写 js 代码是多么的轻松。

数据类型

ES6 中的数据类型是:number,string,boolean,symbol,null,undefined,object

TypeScript 在此基础上,额外增长了:any,void,enum,never

  • any:表示当前这个变量能够被赋予任何数据类型使用;
  • void:表示当前这个变量只能被赋予 null 或 undefined,一般用于函数的返回值声明;
  • enum:枚举类型,弥补 JavaScript 中无枚举的数据类型;
  • never:表示永不存在的值,经常使用于死循环函数,抛异常函数等的返回值声明,由于这些函数永远也不会有一个返回值。

TypeScript 中的数据类型是用于类型声明服务的,相似于 Java 中定义变量或声明方法的返回值时必须指定一个类型。

类型声明

ES5 中声明变量是经过 var,而 ES6 中引入块级做用域后新增了 let 和 const 的声明方式,TypeScript 建议声明变量时,都使用 let,由于 var 会很容易形成不少问题,无论是全局变量仍是函数做用域的局部变量。

先来看看原始类型的声明:

let num:number = 1;      //声明number类型变量
let str:string = "ts";   //声明string类型变量
let is:boolean = true;   //声明boolean类型变量
function f(name: string, age: number):void {  //函数参数类型和返回值类型的声明
    //...
}

声明一个变量时,就能够在变量名后面跟 : 冒号来声明变量的数据类型,若是赋值给变量声明的数据类型以外的类型,编译器会有错误提示;函数的返回值的类型声明方式相似。

若是某个变量的取值能够是任意类型的,那么能够声明为 any:

let variable:any = 1;    //声明可为任意类型的变量
variable = true;//此时赋值其余类型都不会报错

若是某个变量取值只能是某几个类型之间,能够用 | 声明容许的多个类型:

let numStr:number|string = 1;   //声明可为string或number类型变量
numStr = "str";
numStr = true;// 报错

若是变量是个数组:

let numArr:number[] = [1, 2]; //声明纯数字数组,若是某个元素不是数字类型,会报错
let anyArr:any[] = [1, "tr", true];  //数组元素类型不限制
let numStrArr:(number|string)[] = [1, "tr", 2, 4];  // 数组元素类型限制在只能是 number 和 string

若是变量是个对象:

let obj:object = {};

但这一般没有什么意义,由于函数,数组,自定义对象都属于 object,因此能够更具体点,好比声明变量是个函数:

let fun:(a:number)=>string = function (a:number):string {    //声明函数类型的变量
    return "";
}

声明 fun 变量是一个函数类型时,还须要将函数的结构声明出来,也就是函数参数,参数类型,返回值类型,经过 ES6 的箭头函数语法来声明。

但赋值的时候,赋值的函数参数类型,返回值类型能够不显示声明,由于编译器能够根据函数体来自动推断,好比:

let fun:(a:number)=>string = function (a) {
    return "";
}

若是变量是某个自定义的对象:

class Dog {
    name:string;
    age:number = 0;
}

let dog:Dog = new Dog();  //声明自定义对象类型的变量

定义类的语法后面介绍,在 JavaScript 里,鸭式辩型的编程理念比较适用,也就说,判断某个对象是否归属于某个类时,并非看这个对象是不是从这个类建立出来的,而是看这个对象是否具备类的特征,即类中声明的属性,对象是否拥有,有,则认为这个对象是属于这个类的。如:

let dog:Dog = {name:"dog", age:123};  //能够赋值成功,由于对象直接量具备 Dog 类中的属性

let dog1:Dog = {name:"dog", age:1, sex:"male"}; //错误,多了个 sex
let dog2:Dog = {name:"dog"}; //错误,少了个 age
let dog3:Dog = {name:"dog", age:"12"}; //错误,age 类型不同

以上例子中:

let dog1:Dog = {name:"dog", age:1, sex:"male"};

从鸭式辩型角度来讲,这个应该是要能够赋值成功的,由于目标对象拥有类指定的特征行为了,TypeScript 以为额外多出的属性可能会形成问题,因此会给一个错误提示。

针对这种由于额外多出的属性检查而报错的状况,若是想要绕开这个限制,有几种方法:

  • 类型断言
let dog1:Dog = <Dog>{name:"dog", age:1, sex:"male"};
let dog1:Dog = {name:"dog", age:1, sex:"male"} as Dog;

类型断言就是相似 Java 里的强制类型转换概念,经过 <> 尖括号或者 as 关键字,能够告诉编译器这个值的数据类型。

类型断言经常使用于开发者明确知道某个变量的数据类型的状况下。

  • 用变量作中转赋值

若是赋值语句右侧是一个变量,而不是对象直接量的话,那么只会检查变量是否拥有赋值语句左侧所声明的类型的特征,而不会去检查变量额外多出来的属性,如:

let o = {name:"dog", age:1, sex:"male"}; 
let dog1:Dog = o;
  • 剩余属性

这种方式是最佳的方式,官网中对它的描述是字符串索引签名,但我以为这个描述很难理解,并且看它实现的方式,有些相似于 ES6 中的函数的剩余参数的处理,因此我干脆本身给它描述成剩余属性的说法了。

方式是这样的,在类中定义一个用于存储其余没有声明的属性数组:

class Dog {
    name:string;
    age:number = 0;
    [propName:string]:any;
}

最后一行 [propName:string]:any 就表示:具备 Dog 特征的对象除了须要包含 name 和 age 属性外,还能够拥有其余任何类型的属性。因此:

let dog1:Dog = {name:"dog", age:1, sex:"male", s:true};

这样就是被容许的了。

固然,这三种能够绕开多余属性的检查手段,应该适场景而使用,不能滥用,由于,大部分状况下,当 TypeScript 检查出你赋值的对象多了某个额外属性时,程序会所以而出问题的概念是比较大的。

鸭式辩型在 TypeScript 里更经常使用的是利用接口来实现,后续介绍。

接口

鸭式辩型其实严格点来说就是对具备结构的值进行类型检查,而具备结构的值也就是对象了,因此对对象的类型检查,其实也就是在对对象进行类别划分。

既然是类别划分,那么不一样类别固然须要有个标识来相互区分,在 TypeScript 里,接口的做用之一也就是这个,做为不一样对象类别划分的依据。

好比:

interface Dog {
    name:string;
    age:number;
    eat():any;
}

上述就是定义了,对象若是拥有 name, age 属性和 eat 行为,那么就能够将这个对象归类为 Dog,即便建立这个对象并无从实现了 Dog 接口的类上实例化,如:

let dog:Dog = {
    name: "小黑",
    age:1,
    eat: function () {
        //...
    }
}

上述代码声明了一个 Dog 类型的变量,那么什么对象才算是 Dog 类型,只要拥有 Dog 中声明的属性和行为就认为这个对象是 Dog,这就是鸭式辩型。(属性和行为是 Java 里面向对象常说的概念,属性对应变量,行为对应方法,在 JavaScript 里变量和方法都属于对象的属性,但既然 TypeScript 也有相似 Java 的接口和类语法,因此这里我习惯以 Java 那边的说法来描述了,反正能理解就行)

固然,也能够经过定义一个 Dog 类来做为变量的类型声明,但接口相比于类的好处在于,接口里只能有定义,一个接口里具备哪些属性和行为一目了然,而类中经常携带各类逻辑。

既然接口做用之一是用来定义对象的类别特征,那么,它还有不少其余的用法,好比:

interface Dog {
    name:string;
    age:number;
    eat:()=>any;

    master?:string;  //狗的主人属性,无关紧要
    readonly variety:string; //狗的品种,一辈子下来就固定了
}

let dog1:Dog = {name:"dog1", age:1, eat:()=>"", variety:"柯基"};
dog1.age = 2;
dog1.variety = "中华犬";//报错,variety声明时就被固定了,没法更改

let dog2:Dog = {name:"dog2", age:1, eat:()=>"", master: "me",variety:"柯基"};

在接口里声明属性时,可用 ? 问号表示该属性可有也可没有,可用 readonly 来表示该属性为只读属性,那么在定义时初始化后就不能再被赋值。

? 问号用来声明该项无关紧要不只能够用于在定义接口的属性时使用,还能够用于声明函数参数时使用。

在类型声明一节中说过,声明一个变量的类型时,也能够声明为函数类型,而函数本质上也是对象,因此,若是有需求是须要区分多个不一样的函数是否属于同一个类别的函数时,也能够用接口来实现,如:

interface Func {
    (name:string):boolean;
}

let func:Func = function (name) {
    return true;
}

这种使用接口的方式称为声明函数类型的接口,能够简单的理解为,为 Func 类型的变量定义了 () 运算符,需传入指定类型参数和返回指定类型的值。

若是想让某个类型既能够当作函数被调用,又能够做为对象,拥有某些属性行为,那么能够结合上述声明函数类型的接口方式和正常的接口定义属性行为方式一块儿使用。

当对象或函数做为函数参数时,经过接口来定义这些参数的类型,就特别有用,这样能够控制函数调用时传入了预期类型的数据,若是类型不一致时,编译阶段就会报错。

固然,接口除了用来在鸭式辩型中做为值类型的区分外,也能够像 Java 里的接口同样,定义一些行为规范,强制实现该接口的类的行为,如:

interface Dog {
    name:string;
    age:number;
    eat:()=>any;

    master?:string;  //狗的主人属性,无关紧要
    readonly variety:string; //狗的品种,一辈子下来就固定了
}
class ChinaDog implements Dog{
    age: number = 0;
    eat: () => any;
    master: string;
    name: string;
    readonly variety: string = "中华犬";

}

ChinaDog 实现了 Dog 接口,那么就必须实现该接口所定义的属性行为,因此,ChinaDog 建立的对象明显就属于 Dog:

let dog3:Dog = new ChinaDog();

除了这些基本用法外,TypeScript 的接口还有其余不少用法,好比,定义构造函数:

interface Dog {
    new (name:string): Dog;
}

再好比接口的继承:接口可继承自接口,也可继承自类,继承的时候,可同时继承多个等。

更多高级用法,等有具体的使用场景,碰到的时候再深刻去学习,先了解到这程度吧。

Class 语法

习惯 Java 代码后,首次接触 ES5 多多少少会很不适应,由于 ES5 中都是基于原型的继承,没有 class 概念,自定义个对象都是写构造函数,写 prototype。

后来 ES6 中新增了 class 语法糖,能够相似 Java 同样经过 class 自定义对象,但仍是有不少区别,好比,ES6 中的 class 语法糖,就没法声明成员变量,成员变量只能在构造函数内定义和初始化;并且,也没有权限控制、也没有抽象方法机制、也不能定义静态变量等等。

然而,这一切问题,在 TypeScript 中都获得了解决,TypeScript 的 class 语法基本跟 Java 同样,有 Java 基础的,学习 TypeScript 的 class 语法会很轻松。

看个例子:

abstract class Animal {  //定义抽象类
    age:number;
    protected abstract eat():void;  //抽象方法,权限修饰符
}

class Dog extends Animal{   //类的继承
    public static readonly TAG:string = "Dog";  //定义静态常量
    public name:string;
    private isDog:boolean = true;   //定义私有变量

    constructor(name:string) {
        super();
        this.name = name;
        this.age = 0;
    }

    protected eat:()=>any = function () {
        this.isDog;
    }

    get age():number {  //将 age 定义为存取器属性
        return this.age;
    }

    set age(age:number) {  //将 age 定义为存取器属性
        if (age > 0) {
            this.age = age;
        } else {
            age = 0;
        }
    }
}

let dog:Dog = new Dog("小黑");

大概有几个地方跟 Java 有些许小差异:

  • 变量类型的声明
  • 构造函数不是用类名表示,而是使用 constructor
  • 若是有继承关系,则构造函数中必需要调用super
  • 不手动使用权限修饰符,默认是 public 权限

其他方面,无论是权限的控制、继承的写法、成员变量的定义或初始化、抽象类的定义、基本上都跟 Java 的语法差很少。

因此说 TypeScript 的 class 语法比 ES6 的 class 语法糖要更强大。

还有不少细节的方面,好比在构造函数的参数前面加上权限修饰符,此时这个参数就会被当作成员变量来处理,能够节省掉赋值的操做;

好比在 TypeScript 里,类还能够当作接口来使用。更多用法后续有深刻再慢慢记录。

泛型

Java 里在操做实体数据时,常常会须要用到泛型,但 JavaScript 自己并不支持泛型,不过 TypeScript 支持,好比:

interface Adapter<T> {
    data:T;
}

class StringAdapter implements Adapter<string>{
    data: string;
}

function f1<Y extends Animal>(arg:Y):Y {
    return;
}

f1(new Dog("小黑"));

Dog 和 Animal 使用的是上个小节中的代码。

用法基本跟 Java 相似,函数泛型、类泛型、泛型约束等。

模块

JavaScript 跟 Java 很不同的一点就是,Java 有 class 机制,不一样文件都须要有一个 public class,每一个文件只是用于描述一个类的属性和行为,类中的变量不会影响其余文件内的变量,即便有同名类,只要类文件路径不一致便可。

但 JavaScript 全部的 js 文件都是运行在全局空间内,所以若是不在函数内定义的变量都属于全局变量,即便分散在多份不一样文件内,这就很容易形成变量冲突。

因此也才有那么多模块化规范的技术。

虽然 TypeScript 的 class 语法很相似于 Java,但 TypeScript 最终仍旧是要转换成 JavaScript 语言的,所以即便用 TypeScript 来写 class,只要有出现同名类,那么即便在不一样文件内,仍旧会形成变量冲突。

解决这个问题的话,TypeScript 也支持了模块化的语法。

并且,TypeScript 模块化语法有一个好处是,你只需掌握 TypeScript 的模块化语法便可,编译阶段能够根据配置转换成 commonJs, amd, cmd, es6 等不一样模块化规范的实现。

TypeScript 的语法跟 ES6 中的模块语法很相似,只要 ts 文件内出现 import 或 export,该文件就会被当作模块文件来处理,即整个文件内的代码都运行在模块做用域内,而不是全局空间内。

  • 使用 export 暴露当前模块对外接口
//module.ts
export interface StringValidator {
    isAcceptable(s: string): boolean;
}

export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}

class AarCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        //...
    }
}
export { AarCodeValidator };

export 的语法基本跟 ES6 中 export 的用法同样。

若是其余模块须要使用该模块的相关接口:

  • 使用 import 依赖其余模块的接口
import { ZipCodeValidator } from "./module";

let myValidator = new ZipCodeValidator();

若是想描述非 TypeScript 编写的类库的类型,咱们须要声明类库所暴露出的API。一般须要编写 .d.ts 声明文件,相似于 C++ 中的 .h 文件。

.d.ts 声明文件的编写,以及引用时须要用到三斜杠指令:

/// <reference path="./m2.d.ts"/>

这部份内容我还没理解清楚,后续碰到实际使用掌握后再来讲说。

命名空间

命名空间与模块的区别在于,模块会涉及到 import 或 export,而命名空间纯粹就是当前 ts 文件内的代码不想运行在全局命名空间内,因此能够经过 命名空间的语法,让其运行在指定的命名空间内,防止污染全局变量。

语法:

namespace Validation {
    //...
}

其余

本篇只讲了 TypeScript 的一些基础语法,还有其余更多知识点,好比引入三方不是用 TypeScript 写的库时须要编写的 .d.ts 声明文件,好比编译配置文件的各类配置项,好比枚举,更多更多的内容,请参考开头声明部分给出的 TypeScript 中文网链接。


你们好,我是 dasu,欢迎关注个人公众号(dasuAndroidTv),公众号中有个人联系方式,欢迎有事没事来唠嗑一下,若是你以为本篇内容有帮助到你,能够转载但记得要关注,要标明原文哦,谢谢支持~
dasuAndroidTv2.png

相关文章
相关标签/搜索