揭秘Angular 2(一):TypeScript入门
来源:互联网 发布:淘宝客没权重吗 编辑:程序博客网 时间:2024/05/21 22:54
3.1 TypeScript概述
3.1.1概述
3.1.2安装
- 检查
npm
是否已经安装 接下来我们使用2.0正式版的TypeScript
,安装命令如下:
npm install -g typescript@2.0.0
写第一个Typescript
程序,并保存到hello.ts
中,文件中代码:
console.log('hello TypeScript!');
通过tsc
来编译TypeScript
文件:
tsc hello.ts
3.2基本类型
- 布尔类型(boolean)
- 数字类型(number)
- 字符串类型(string)
- 数组类型(array)
元祖类型(typule)
枚举类型(enum)
任意值类型(any)
- null和undefined
void类型
never类型
3.2.1 布尔类型
let flag:boolean=true;flag=1;//报错,不能把数字类型的值赋给布尔类型的变量
3.2.2 数字类型
let binaryLiteral:number=0b1010;//二进制let octalLiteral:number=0b1010;//八进制let decLiteral:number=6;//十进制let hexLiteral:number=0xfood;//十六进制
3.2.3 字符串类型
let name:string=”Angular”;
let years:number=5;
let words:string=你好,今年是 ${name} 发布 ${ years +1} 周年
;
3.2.4 数组类型
// 在元素类型后面接上[]let arr:number[]=[1,2];//或者使用数组泛型let arr:Array<number>=[1,2];
3.2.5 元组类型
元祖类型用来表示已知元素数量和类型的数组,各元素的类型不必相同
let x:[string,number];x=['Angular',25];//运行正常x=[10,'Angular'];//报错console.log(x[0]); //输出 Angular
3.2.6 枚举类型
枚举是一个可被命名的整型常熟的集合,枚举类型为集合成员赋予有意义的名称,增强可读性
enum Color {Red,Green,Blue};let c:Color=Color.Blue;console.log(c); //输出:2
枚举默认下标是0,可以手动修改默认下标值:
enum Color {Red=2,Green,Blue=6};let c:Color=Color.Blue;console.log(c); //输出:3
3.2.7 任意值类型
3.2.8 null和undefined
3.2.9 void类型
void
表示没有任何类型
函数没有返回值时,返回值类型是void
function hello():void{
alert(“Hello Angular”);
}
对于可以忽略返回值的回调函数来说,使用void
类型比任意值类型更完全一些
function func(foo:()=>void){
let f=foo();//使用函数foo的返回值
f.doSth();//报错,void
类型不存在doSth()
方法,此时换成任意值类型则不报错
}
3.2.10 never类型
never
是其它类型(包括null
和undefined
)的子类型,代表从不会出现的值
let x:never;let y:number;//运行错误,数字类型不能转化为`never`类型x=123;//运行正确,never类型可以赋值给never类型x=(() => { throw new Error('exception occur')})();//运行正确,never类型可以赋值给数字类型y=(() => { throw new Error('exception occur')})();//返回值为`never`的函数可以是抛出异常的情况function error(message:string):never{ throw new Error(message);}//返回值为`never`的函数可以是无法被执行到的终止点的情况function loop():never{ while(true){ }}
3.3声明和解构
- 在TypeScript中,支持
var
、let
和const
这样的声明方式
3.3.1 let声明
let
与var
声明变量的写法类似:
let hello="Hello Angular";
let
声明的变量只在块级作用域内有效:
function f(input: boolean) { let a=100; if(input) { let b=a+1;//运行正确 return b; } return b;//错误,b没有被定义}
块级作用域不能在它声明之前被读取或赋值
a++;//错误,在声明之前使用是不合法的let a;
let
不允许被重复声明
var x=2;console.log(x+3);//输出:5var x=3;console.log(x+3);//输出:6let y=2;let y=3;//报错,let声明的变量不能在一个作用域里多次声明
let
声明在下面两种函数入参的对比:
function funA(X) { let x=100;//报错,x已经在函数入参声明}//增加了判断条件组成的新的块级作用域function funB(condition,x) { if(condition){ let x=100;//运行正常 return x; } return x;}funB(false,0);//返回0funB(true,0);//返回100
3.3.2 const声明
const
声明与let
声明相似,但const
声明的是常量
,常量
不能被重新赋值,否则将编译错误。但如果定义的常量
是对象
,对象
里的属性值
是可以被重新赋值的:
const CAT_LIVES_NUM=9;const kitty={ name:"Aurora", numLives:CAT_LIVES_NUM};//错误kitty={ name:"Danielle", numLives:CAT_LIVES_NUM};kitty.name="Kitty"; //正确kitty.numLives--; //正确
3.3.3 解构
解构就是将声明的一组变量与相同结构的数组或者对象的元素数值一一对应,并将变量相对应元素进行赋值。
数组解构
let input=[1,2];let [first,second]=input;console.log(first); //相当于 input[0]: 1console.log(second); //相当于 input[1]: 2
也可作用于已声明的变量:
[first,second]=[second,first]; //变量交换
或作用于函数参数:
function f([first,second]=[number,number]) { console.log(first+second);}f([1,2]); //输出3
在数组解构中使用rest
参数语法
let [first, ...rest]=[1,2,3,4];console.log(first);//输出1console.log(rest);//输出:[2,3,4]
对象解构
let test={x:0,y:0,width:15,height:20};let {x,y,width,height}=test;console.log(x,y,width,height);//输出: 0,10,15,20
3.4 函数
3.4.1 函数定义
- 在
TypeScript
中支持函数声明和函数表达式的写法,示例代码如下:
//函数声明写法
function maxA(x:number,y:number):number { return x>y?x:y;}
//函数表达式写法
let maxB=function(x:number,y:number):number {return x>y?x:y;}
3.4.2 可选参数
在TypeScript
中,被调函数的每个参数都是必传的
function max(x:number,y:number):number { return x>y?x:y;}let result1=max(2);//报错let result2=max(2,4,7);//报错let result3=max(2,4);//运行正常
Typescript
提供了可选参数语法
function max(x:number,y?:number):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);//运行正常
3.4.3 默认参数
Typescript
还支持初始化默认参数,如果没有给这个参数传值或者传的值为undefined
时,这个参数的值就是设置的默认值:
function max(x:number,y=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,4);//运行正常
如果默认值参数放到了必选参数的前面,用户必须显示地传入undefined
:
function max(x=2,y:number):number { return x>y?x:y;}let result1=max(2);//报错let result2=max(undefined,4);//运行正常let result3=max(2,4,7);//报错let result4=max(2,4);//运行正常
3.4.4 剩余参数
当需要同时操作多个参数,或者并不知道会有多少参数传递进来时,就需要用到Typescript
里的剩余参数
function sum(x:number,...restOfNumber:number[]):number { let result=x; for(let i=0;i<restOfNumber.length;i++){ result+=restOfNumber[i]; } return result; }let result=sum(1,2,3,4,5);console.log(result);//输出:15
3.4.5 函数重载
函数重载通过为同一个函数提供多个函数类型定义来达到实现多种功能的目的。
function css(config:{});function css(config:string,value:string);function css(config:any,valu?:any){ if(typeof config==='string') { //... } else if(typeof config==='object') { //... }}
3.4.6 箭头函数
let gift={ gifts:["teddy bear","spiderman","dinosaur","Thomas loco","toy ricks","Transformers"], giftPicker:function () { return function () { let pickedNumber=Math.floor(Math.random()*6); return this.gifts[pickedNumber]; } }}let pickGift=gift.giftPicker();console.log("you get a :"+pickGift());//Uncaught TypeError: Cannot read property '1' of undefined
上面giftPicker()
函数里的this
被设置成了window
而不是gift
对象,因为这里没有对this
进行动态绑定,因此this
就指向了window
对象。
let gift={ gifts:["teddy bear","spiderman","dinosaur","Thomas loco","toy ricks","Transformers"], giftPicker:function () { return ()=> { let pickedNumber=Math.floor(Math.random()*6); return this.gifts[pickedNumber]; } }}let pickGift=gift.giftPicker();console.log("you get a :"+pickGift());
- 上面代码
TypeScript
提供的箭头函数(=>
)很好地解决了这个问题,它在函数创建时就绑定了this
。
3.5 类
3.5.1 类的例子
传统的javascript
程序使用函数和基于原型继承来创建可重用的”类”,这对于习惯了面向对象编程的开发者来说不是很友好。TypeScript
中可以支持使用基于类的面向对象编程。
看下面例子:
class Car{ engine:string; constructor(engine:string){ this.engine=engine; } drive(distanceInMeters:number=0){ console.log(`A car runs ${distanceInMeters}m powered by` +this.engine); }}
上面声明一个汽车类Car
,这个类有三个类成员:
其中类属性engine
,可通过this.engine
访问
实例化一个car
新对象,并执行构造函数初始化:
let car =new Car("petrol");car.drive(100);//输出:A car runs 100m powered by petrol
3.5.2 继承与多态
封装
、继承
、多态
是面向对象的三大特性。- 上面的例子中把汽车的行为写到一个类中,即所谓的封装。
在TypeScript
中,使用extends
关键字即可方便地实现继承:
//继承自前文的 Car 类class MotoCar extends Car{ constructor(engine: string) { super(engine); }}class Jeep extends Car { constructor(engine:string){ super(engine); } drive(distanceInMeters:number=100){ console.log("Jeep..."); return super.drive(distanceInMeters); }}let tesla=new MotoCar("electricity");let landRover: Car=new Jeep("petrol"); //实现多态tesla.drive();//调用父类的drive()方法landRover.drive(200);//调用子类的 drive() 方法
MotoCar
和Jeep
是基类Car
的子类,通过extends
来继承父类- 子类可以访问父类的属性和方法,也可以重写父类的方法
Jeep
的drive()
方法重写了Car
的drive()
方法,这样的drive()
方法在不同的类中具有不同的功能,这就是多态
。- 即使
landRover
被声明为Car
类型,它依然是子类Jeep
,landRover.drive(200)
调用的是Jeep
里的重写方法 - 派生类构造函数须调用
super()
,它会执行基类的构造方法
3.5.3 修饰符
类中的修饰符三种类型:
- 公共(public)
- 私有(private)
- 受保护(protected)
public修饰符
在TypeScript
里,每个成员默认为`public`,可以被自由地访问:
class Car{ public engine:string; public constructor(engine:string){ this.engine=engine; } public drive(distanceInMeters:number=0){ console.log(`A car runs ${distanceInMeters}m powered by` +this.engine); }}
private修饰符
protected修饰符
class Car{ protected engine:string; constructor(engine:string){ this.engine=engine; } drive(distanceInMeters:number=0){ console.log(`A car runs ${distanceInMeters}m powered by` +this.engine); }}class MotoCar extends Car{ constructor(engine: string) { super(engine); } drive(distanceInMeters: number =50){ super.drive(distanceInMeters); }}let tesla=new MotoCar("electricity");console.log(tesla.drive()); //运行正常,输出: A car runs 50m powered by electricityconsole.log(tesla.engine); //报错
- 注意,由于
engine
被声明为protected
,所以不能在外部访问它,但是仍然可以通过它的继承类MotoCar
来访问
3.5.4 参数属性
参数属性是通过给构造函数参数添加一个访问限定符(public
、protected
以及provate
)来声明。
class Car{ constructor(protected engine:string){} drive(distanceInMeters:number=0){ console.log(`A car runs ${distanceInMeters}m powered by` +this.engine); }}
- 在构造函数里通过
protected engine:string
来创建和初始化engine
成员属性,从而把声明和赋值合并到一处。
3.5.5 静态属性
类的静态成员存在于类本身而不是类的实例上
class Grid { static origin={x:0,y:0}; constructor (public scale:number) {} calculateDistanceFromOrigin(point: {x:number;y:number;}){ let xDist=(point.x-Grid.origin.x); let yDist=(point.y-Grid.origin.y); return Math.sqrt(xDist*xDist+yDist*yDist)/this.scale; }}let grid1= new Grid(1.0);let grid2= new Grid(5.0);console.log(grid1.calculateDistanceFromOrigin({x:10,y:10})); //输出: 14.142135623730951console.log(grid2.calculateDistanceFromOrigin({x:10,y:10})); //输出: 2.8284271247461903
3.5.6抽象类
TypeScript
有抽象类的概念,它是供其它类继承的基类,不能直接被实例化。抽象类必须包含一些抽象方法,同时也可以包含非抽象的成员。abstract
关键字用于定义抽象类和抽象方法。
abstract class Person { abstract speak():void;//必须在派生类中实现 walking(): void { console.log('walking on the road'); } class Male extends Person { speak(): void{ console.log('How are you?'); } }}let person:Person; //创建一个抽象类引用person=new Person(); //报错:不能创建抽象类实例person=new Male(); //创建一个Male实例person.speak();person.walking();
3.6 模块
3.6.1 概述
- 模块在自身的作用域里执行,而不是在全局作用域里,这意味着定义在一个模块里的变量、函数和类等,在模块外是不可见的,除非明确地使用
export
导出它们。反之,则必须通过import
导入它们。 - 在
Angular
中,常见的模块加载器有SystenJS
和Webpack
3.6.2 模块导出方式
导出声明
export const COMPANY="GF";//导出变量export interface IdentityValidate{ //导出接口 isGfStaff(s: string): boolean;}export class ErpIdentityValidate implements IdentityValidate { //导出类 isGfStaff(erp: string) { return erpService.contains(erp); //判断是否为内部员工 }}
导出语句(当我们需要对导出的模块进行重命名时,就用到了导出语句)
class ErpIdentityValidate implements IdentityValidate { //导出类 isGfStaff(erp: string) { return erpService.contains(erp); }}export { ErpIdentityValidate };export { ErpIdentityValidate as GFIdentityValidate };
模块包装(修改和扩展已有的模块,并导出供其他模块调用)
//导出原先的验证器,但做了重命名 export { ErpIdentityValidate as RegExpBasedZipCodeValidator} from "./ErpIdentityValidate";
3.6.3 模块导入方式
3.6.4 模块的默认导出
3.6.4 模块设计原则
3.7接口
3.7.1 概述
3.7.2 属性类型接口
在TypeScript
中,使用interface
关键字来定义接口
interface FullName { firstName: string; secondName: string;}function printLable(name: FullName) { console.log(name.firstName + " "+name.secondName);}let myObj = {age:10,firstName:"Jim",secondName:"Raynor"};printLable(myObj);
- 传给
printLable()
方法的对象只要”形式上”满足接口的需求即可 - 接口类型检查器不会去检查属性顺序,但要确保相应的属性存在且类型匹配
TypeScript
还提供了可选属性
interface FullName { firstName: string; secondName?: string;}function printLable(name: FullName) { console.log(name.firstName + " "+name.secondName);}let myObj = {firstName:"Jim"}; //secondName 是可选属性,可以不传printLable(myObj); //输出: Jim undefined
3.7.3 函数类型接口
定义函数类型接口时,需要明确定义函数的参数列表
和返回值类型
,且参数列表的每个参数都要有参数名
和类型
:
interface encrypt { (val:string,salt:string):string}
如何使用函数类型接口:
let md5: encrypt;md5=function (val:string,salt:string) { console.log("origin value:" +val); let encryptValue =doMd5(val,salt);//doMd5只是个mock方法 console.log("encrypt value:" +encryptValue); return encryptValue;}let pwd =md5("password","Angular");
- 函数类型的接口需要注意:
- 使用时参数个数需与接口定义的参数相同,对应位置变量的数据类型需保持一致,参数名可以不一样
- 函数的额返回值:函数的返回值类型与接口定义的返回值类型要一致。
3.7.4 可索引类型接口
通过特定的索引来得到指定类型的返回值:
interface UserArray { [index: number]: string;}interface UserObject { [index: string]: string;}let userArray: UserArray;let UserObject: UserObject;userArray =["张三","李四"];userObject =["name","张三"];console.log(userArray[0]); //输出: 张三console.log(userObject["name"]); //输出: 张三
即使是用数字类型
来索引,javascript最终也会将它转换成字符串类型
然后再去索引对象的
console.log(userArray[0]); //输出: 张三console.log(userArray["0"]); //输出: 张三
3.7.5 类类型接口
3.7.6 接口扩展
和类一样,接口也可以实现相互扩展,即能将成员从一个接口复制到一个页面,这样可以更灵活地将接口拆分到可复用的模板里:
interface Animal { eat(); void;}interface Person extends Animal { talk(); void;}class Programmer { coding():void{ console.log('wow,TypeScript is the best language'); }}class ITGirl extends Programmer implements Person { eat() { console.log('animal eat'); } talk(){ console.log('person talk'); } coding():void { console.log('I am girl,but i like coding.'); }}let itGirl =new ITGirl(); //通过组合集成类来实现接口扩展,可以更灵活复用模块itGirl.coding();
3.8 装饰器
3.8.1 概述
- 装饰器是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上。
- 装饰器由
@
符号紧跟一个函数名称 装饰器是用来给附着的主体进行装饰,添加额外的行为
//方法装饰器declare type MethodDecorarot =<T>(target: Object,propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>)=> TypedPropertyDescriptor<T> | void;//类装饰器declare type ClassDecorator =<TFunction extends Function>(target: TFunction) => TFunction | void;//参数装饰器declare type ParameterDecorator = ( target: Object, propertyKey: string | symbol,parameterIndex: number) => void;//属性装饰器declare type PropertyDecorator = ( target: Object, propertyKey: string | symbol) => void;
3.8.2 方法装饰器
方法装饰器是声明在一个方法之前被声明的(紧贴着方法声明),它会被应用到方法的属性描述符上,可以用来监视、修改或替换方法定义。方法装饰器的声明如下:
declare type MethodDecorator = <T>(target: Object,propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
方法装饰器表达式会在运行时当作函数被调用,传入下列三个参数:
- target: 类的原型对象
- propertyKey: 方法的名字
- descriptor: 成员属性描述
其中,descriptor
的类型为TypedPropertyDescriptor
,在TypeScript
中定义如下:
interface TypedPropertyDescriptor<T> { enumerable?: boolean; //是否可遍历 configurable?: boolean; //属性描述是否可改变或属性是否可删除 writable?: boolean; //是否可修改 value?: T; //属性的值 get?: ()=>; //属性的访问器函数(getter) set?: (value: T) => void;//属性器的设置器函数(setter)}
方法装饰器的例子:
class TestClass { @log testMethod(arg: string) { return "logMsg: " +arg; }}
下面是方法装饰器@log的实现:
function log(target: Object,propertyKey: string,descriptor: TypedPropertyDescriptor<any>) { let origin =descriptor.value; descriptor.value=function(...args: any[]) { console.log("args: "+JSON.stringify(args)); //调用前 let result =origin.apply(this,args); //调用方法 console.log("The result-" + result); //调用后 return result; //返回结果 } return descriptor;}
然后可以使用以下代码进行测试:
new TestClass().testMethod("test method decorator");
结果输出如下:
args: ["test method decorator"]The result-logMsg: test method decorator
当方法装饰器 @log 被调用时,它会打印日志信息。
3.8.3 类装饰器
3.8.4 参数装饰器
参数装饰器是在声明一个参数之前被声明的,它用于类的构造函数或方法声明。参数装饰器会在运行时会被当作函数的形式来调用,定义如下:
declare type ParameterDecorator =(target: Object,propertyKey: string | symbol,parameterIndex: number) => void;
如上所述,包含3个参数:
target
: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。propertyKey
: 参数名称。parameterIndex
: 参数在函数参数列表中的索引。
下面是参数装饰器的一个简单例子:
class userService { login(@inject name: string) {}}// @inject 装饰器的实现function inject (target: any,propertyKet: string | symbol,parameterIndex: number) { console.log(target); //userService prototype console.log(propertyKey); console.log(parameterIndex); } // 结果输出如下:Ojectlogin0
3.8.5 属性装饰器
3.8.6 装饰器组合
TypeScript
支持多个装饰器同时应用到一个声明上:
在TypeScript
里,当多个装饰器应用在同一个声明上的时候,会进行如下步骤的操作:
- 从左到右(从上到下)依次执行装饰器函数,得到返回结果
- 返回结果会被当做函数,从左到右(从上到下)依次调用
下面是两个类装饰器复合应用的例子,注意看输出结果所显示的执行顺序,示例代码如下:
function Component (component) { console.log('selector: '+component.selector); console.log('template: '+component.tempalte); console.log('component init'); return (target: any) => { console.log('component call'); return target; }}function Directive (directive) { console.log('directive init'); return (target: any) => { console.log('directive call'); return target; }}@Component({ selector: 'person', template: 'person.html'})@Directive()class Person {}let p= new Person();
结果输出如下:
selector: person template: undefined component init directive init directive call component call
3.9 泛型
- 在实际开发中,我们定义的API不仅仅需要考虑功能是否健全,还需要考虑到它的复用性,更多的时候需要支持不特定的数据类型,而
泛型
就是用来实现这样的效果。 下面最小堆
算法,需要同时支持数字
和字符串
两种类型,可以通过把集合类型改为任意值类型(any)来实现,但是这样就等于放弃了类型检查,我们希望的是返回的类型需要和参数类型一致,示例代码如下:
class MinHeap<T> { list: T[]=[]; add(element: T): void { //这里进行大小比较,并将最小值放在数组头部 } min(): T { return this.list.length ? this.list[0] : null; } } let heap1 =new MinHeap<number>(); heap1.add(3); heap1.add(5); console.log(heap1.min()); let heap2 =new MinHeap<string>(); heap2.add('a'); heap2.add('c'); console.log(heap2.min());
泛型也支持函数
function zip<T1,T2> (l1: T1[], l2: T2[]):[T1,T2][] { let len =Math.min(l1.length, l2.length); let ret=[]; for (let i=0;i<len;i++){ ret.push([l1[i], l2[i]]); } return ret; } console.log(zip<number, string>([1,2,3],['Jim','Sam','Tom']));
3.10 TypeScript周边
3.10.1 编译配置文件
3.10.2 声明文件
0 0