angular 2是有一门像Javascript的语言Typescript编写的。
你可能会觉得奇怪,angular 2为什么使用一门新的语言。但是,最终证明这是一个伟大的决定,这里有许多理由去使用Typescript而不是JavaScript。
Typescript不完全是一门新的语言,它是ES6的超集。如果你写过ES6的代码,它是完全有效并且兼容Typescript的。这里有一张图展示了这些语言之间的关系:
:fa-info-circle: ES5、ES6是什么?ES5是“ECMAScript5”的简称,它是被叫做“正规JavaScript”而出名,ES5是我们知道并且喜欢的通常的JavaScript,ES6是下一代JavaScript。我们下面会经常讨论到。
在这本书发布的时候,只有少数浏览器能够运行ES6,更不用说Typescript了,所以我们需要一个翻译器。它将Typescript翻译为ES5,从而使得所有的浏览器都能够理解。
:fa-info-circle: 为了将Typescript转换成ES5,Typescript团队编写了一个简单的翻译器。然而,如果你希望将ES6代码转换成ES5而不是Typescript,这里有两个选择:Google出品的traceur和JavaScript社区出品的babel。在这本书中,我们不会去直接使用它们,但是它们都是值得了解的好的项目。
Typescript是Google与Microsoft合作的一个项目成果。这是一个激动人心的消息,因为这意味着它背后有两家顶级的科技公司提供长期的支持,不仅会推动web的发展,而且我们也会从中受益。
关于翻译器的一个意义就是,它只要求一个小的团队来改进语言,而不需要众多的用户来升级它们的浏览器。
有一点必须指出:我们不是必须用Typescript来编写angular2的应用程序。如果你想用ES5来写,完全没有问题。为什么我们会使用Typescript呢?因为它有很多特性来提供我们的效率。
相对于ES5来说,Typescript有下面五个大的提升:
- 类型(types)
- 类(classes)
- 注解(annotations)
- 导入(import)
- 语言工具(比如:解构)
让我们来详细了解一下。
Typescript相对于ES6来说主要的提升就是类型系统。
对于一些人来说,类型的缺失是JavaScript的好处,对于类型检查你可能持怀疑太多,但是我们建议你给它一个机会,类型检查最主要的好处如下:
- 通过编译时检查它可以帮助在编写代码的时候预防bug
- 阅读代码的时候更加清楚
值得注意的是,在Typescript中,类型是可选的。如果我们需要一个快速的原型,可以删除类型信息,在将来的时候慢慢加上。
Typescript的基本类型跟我们JavaScript中的类型是相同的,比如:string、number、boolean等。
直到ES5,我们定义类型通过var关键字,比如:var name;
Typescript的变量定义来源于ES5,但是我们可以提供那个变量的类型,在它的名字后面加上冒号,再加上类型信息。
var name:string;
当声明函数的时候,我们可以为参数与返回值添加类型.
function greetText(name:string):string{
return "Hello " + name;
}
在上面的例子中,我们定义了一个新的函数greetText,它带一个string类型的name的参数,并且返回string.注意name:string代表的是name参数的类型是string,在括号后面的:string代表的是这个函数的返回类型是string.如果我们调用greetText传递的不是一个string参数,在编译不会通过.这个是我们希望的,不然它会导致一个潜在的bug.
让我们看看,如果你写的代码与我们声明的类型不一样会导致什么问题:
function hello(name: string): string {
return 12;
}
如果你试着编译它,你可以得到下面的错误:
$ tsc compile-error.ts
compile-error.ts(2,12): error TS2322: Type 'number' is not assignable to type 'string'.
发生了什么?我们试着返回的12是一个number类型,但是hello定了一个string的返回类型.
为了修正这个错误,我们可以将12转换成一个string,或者将函数声明修改为返回number,如下:
function hello(name: string): number {
return 12;
}
这是一个简单的例子,但是也可以看到,通过使用类型,在前期可以为我们发现很多的bug.
到现在为止,我们知道了怎么使用类型,我们可以使用的类型有哪些呢?先让我们了解一下內建类型,然后我们学习怎么创建自定义的类型.
为了演示这一章的小例子,我们介绍一款工具,叫TSUN(TypeScript Upgrade Node):
$ npm install tsun
现在,启动tsun:
$ tsun
TSUN : TypeScript Upgraded Node
type in TypeScript expression to evaluate
type :help for commands in repl
>
出现>符号说明tsun已经准备好了,下面的绝大多数例子我们都可以通过复制粘贴到这个终端运行.
代表一个字符串:
var name: string = 'Felipe';
代表数字,所有的数都是使用浮点数表示:
var age: number = 36;
代表true与false两个数
var married: boolean = true;
数组,我们也需要标明数组元素的类型,可以使用Array或者Type[]两种方式来表示:
var jobs: Array<string> = ['IBM', 'Microsoft', 'Google'];
var jobs: string[] = ['Apple', 'Dell', 'HP'];
var jobs: Array = [1, 2, 3];
var jobs: number[] = [4, 5, 6];
enum是一个枚举,它使用命名参数来代表数值.比如,我们想要有一个角色列表,我们可能会像下面这样写:
enum Role {Employee, Manager, Admin};
var role: Role = Role.Employee;
enum默认的初始值为0,你也可以标明开始值:
enum Role {Employee = 3, Manager, Admin};
var role: Role = Role.Employee;
在上面的例子中,Employee不是0,而是3,后面的跟随定义的值相应增加1.也就是说,Manager是4,Admin是5.当然,你也可以为每一个单独设置一个值:
enum Role {Employee = 3, Manager = 5, Admin = 7};
var role: Role = Role.Employee;
你也可以寻找给定枚举的名字而不是它的值:
enum Role {Employee, Manager, Admin};
console.log('Roles: ', Role[0], ',', Role[1], 'and', Role[2]);
any是一个默认类型,如果不需要标明变量的类型的时候.一个变量如果是any,表示它可以接收任何类型的值.
var something: any = 'as string';
something = 1;
something = [1, 2, 3];
void表示没有值,这个通常使用在函数没有返回值的情况:
function setName(name:string):void{
this.name = name;
}
在ES5中,面向对象的编程是通过基于原型的对象完成的.这个模型不适用类,但是依赖的是原型.
但是在ES6中,类被内建支持了.为了顶一个类,我们可以使用class关键字和类的名字及类的主题组成:
class Vehicle {
}
类可以有属性,方法及构造器.
属性定义了依附在类实例上的数据,比如一个Person类可能有firstname,lastname和age属性.可以定义如下:
class Person {
firstName:string;
lastName:string;
age:number;
}
方法是一个类实例上面可以操作的行为.比如:
class Person {
first_name: string;
last_name: string;
age: number;
greet() {
console.log("hello " + this.firstName);
}
}
注意,我们可以使用this关键字去访问firstName,如果没有标明函数的返回类型,则代表可以返回任意类型,本例子中,就是返回void,也就是没有返回任意值.
要调用greet函数,你需要像下面这样:
// declare a variable of type Person
var p: Person;
// instantiate a new Person instance
p = new Person();
// give it a first_name
p.first_name = 'Felipe'; // call the greet method
p.greet();
构造器是一类特殊的函数,它在类创建一个新实例的时候被调用,通常,构造器执行一些初始化的操作.
构造器函数必须被命名为constructor.它的参数是可选的,但是不能返回任何类型.
当一个类没有显示声明constructor时,我们会自动隐士创建一个:
class Vehicle{}
var v= new Vehicle()
它等同于:
class Vehicle{
constructor(){
}
}
var v= new Vehicle();
constructor可以添加任何的参数,比如:
class Person {
first_name: string;
last_name: string;
age: number;
constructor(first_name: string, last_name: string, age: number) {
this.first_name = first_name;
this.last_name = last_name;
this.age = age;
}
greet() {
console.log("Hello", this.first_name);
}
ageInYears(years: number): number {
return this.age + years;
}
}
面向对象编程另一个重要的方面就是继承.继承是一个类从其父类接收数据和行为的一种方式.然后我们可以在新类中重载,修改或者增加这些行为.
Typescript提供了完整的支持,不像ES5,Typescript是内建支持的.重载时通过关键字extends完成的.
为了描述这个,看下面的例子:
class Report {
data: Array<string>;
constructor(data: Array<string>) { this.data = data; }
run() {
this.data.forEach(function (line) { console.log(line); });
}
}
var r: Report = new Report(['First line', 'Second line']);
r.run();
这个程序会输出:
First line
Second line
现在,我们希望去实现一个头不和另外一些数据的报告,但是希望从Report类继承,我们可以像下面这样实现:
class Report {
data: Array;
constructor(data: Array) { this.data = data; }
run() {
this.data.forEach(function (line) { console.log(line); });
}
}
class TabbedReport extends Report {
headers: Array;
constructor(headers: string[], values: string[]) {
super(values);
this.headers = headers;
}
run() {
console.log(this.headers);
super.run();
}
}
var headers: string[] = ['Name'];
var data: string[] = ['Alice Green', 'Paul Pfifer', 'Louis Blakenship'];
var r: TabbedReport = new TabbedReport(headers, data)
r.run();
Typescript提供了很多语法特性来提供开发效率,最重要的两个就是:
- 箭头函数(fat arrow function syntax)
- 模板字符串
箭头函数是使用 => 来简化函数定义的一种方式.
在ES5中,不关我们是传递函数参数,必须使用function关键字和一对大括号{}
// ES5-like example
var data = ['Alice Green', 'Paul Pfifer', 'Louis Blakenship'];
data.forEach(function(line) { console.log(line); });
如果使用=>,我们可以像下面这样:
// Typescript example
var data: string[] = ['Alice Green', 'Paul Pfifer', 'Louis Blakenship']; data.forEach( (line) => console.log(line) );
=>可以作为表达式:
var evens = [2,4,6,8];
var odds = evens.map(v => v + 1);
或者作为语句:
data.forEach( line => {
console.log(line.toUpperCase())
});
箭头函数的一个重要作用就是共享this,这个跟JavaScript不一样,如下:
varnate = {
name: "Nate",
guitars: ["Gibson", "Martin", "Taylor"], printGuitars: function () {
var self = this;
this.guitars.forEach(function (g) {
// this.name is undefined so we have to use self.name
console.log(self.name + " plays a " + g);
});
}
};
因为=>可以共享this,所以我们可以像下面这样:
varnate = {
name: "Nate",
guitars: ["Gibson", "Martin", "Taylor"], printGuitars: function () {
this.guitars.forEach((g) => {
console.log(this.name + " plays a " + g);
});
}
}
=>可以让你的内部函数更加清晰,它使得在JavaScript中使用高阶函数变得容易.
在ES6中,新的模板字符串被介绍,模板字符串有两个重要的特性:
- 字符串中的变量
- 多行字符串
字符串中的变量也称为”字符串插值”,你可以在你的字符串中插入变量的方式:
var firstName = "Nate";
var lastName = "Murray";
// interpolate a string
var greeting = `Hello ${firstName} ${lastName}`;
console.log(greeting);
记住,使用字符串插值必须使用反引号`,不是单引号或者双引号.
另一个重要的特性就是多行字符:
var template = `
<div>
<h1>Helloh1>
<p>This is a great websitep>
div
`
当我们需要将strings放入我们的代码中的时候并且这个字符比较长的时候,多行字符提供了很大的帮助.
Typescript/ES6有其他一些重要的特性:
- 接口
- 泛型
- 导入和导出模块
- 注解
- 解构(destructuring)
在本书中,我们会碰到他们,但是现在,有这些基础知识就可以了.
现在,让我们返回angular的学习.