常见的如下:
// primitive type annotation
var name: string = 'Steve';
var heightInCentimeters: number = 182.88;
var isActive: boolean = true;
// array type annotation
var names: string[] = ['James', 'Nick', 'Rebecca', 'Lily'];
// function annotation with parameter type annotation and return type annotation
var sayHello: (name: string) => string;
// implementation of sayHello function
sayHello = function (name: string) {
return 'Hello ' + name;
};
// object type annotation
var person: { name: string; heightInCentimeters: number; };
// Implementation of a person object
person = {
name: 'Mark',
heightInCentimeters: 183
};Note: although many languages specify the type before the identifier, the placement of type annotations in typeScript after the identifier helps to reinforce that the type annotation is optional. this style of type annotation is also inspired by type theory【Typescript的类型标注与其他编程语言不同,其在变量名之后,因为类型标注是可选的】
If a type annotation becomes too complex, you can create an interface to represent the type to simplify annotations. This technique is especially useful if you intend to reuse the type as it provides a re-usable definition. Interfaces are not limited to describing object types; they are flexible enough to describe any structure you are likely to encounter.【使用接口可以复用类型标注,并且可以拓展任何你想标注的类型】
for example:
interface Person {
name: string;
heightInCentimeters: number;
}
var person: Person = {
name: 'Mark',
heightInCentimeters: 183
}The type system also contains three types that are not intended to be used as type annotations but instead refer to the absence of values.【三个特殊的类型-undefined,null,void】
• The undefined type is the value of a variable that has not been assigned a value. • The null type can be used to represent an intentional absence of an object value. For example, if you had a method that searched an array of objects to find a match, it could return null to indicate that no match was found. • The void type is used only on function return types to represent functions that do not return a value or as a type argument for a generic class or function.
【undefined一般用于未赋值的变量,null一般用于空对象,void一般用于函数无返回值】
interface Monument {
name: string;
heightInMeters: number;
}
// The array is typed using the Monument interface
var monuments: Monument[] = [];//or var monuments:Array<Monument> = [];
// Each item added to the array is checked for type compatibility
monuments.push({
name: 'Statue of Liberty',
heightInMeters: 46,
location: 'USA'
});Caution: This is even the case for the Statue of Liberty object, which has a location property that isn’t part of the Monument interface. This is an example of structural typing, which is explained in more detail in Chapter 2.【虽然实例对象中的location属性并未在Monument接口中出现,但是允许使用,具体用法在第二章讲解】
Bit flags allow a series of items to be selected or deselected by switching individual bits in a sequence on and off. 【位枚举用于标记,且值只能为2的幂次,原因是n位数的最高位为1,其他位为0,如0,1,2,4...在进行位运算的时候才会准确】
enum DiscFlags {
None = 0,
Drive = 1,
Influence = 2,
Steadiness = 4,
Conscientiousness = 8
}
// Using flags
var personality = DiscFlags.Drive | DiscFlags.Conscientiousness;
// Testing flags
// true
var hasD = (personality & DiscFlags.Drive) == DiscFlags.Drive;
//note:移除使用^操作符
personality = personality ^ DiscFlags.Drive;
//false
hasD = (personality & DiscFlags.Drive) == DiscFlags.Drive;由上可知,|操作符用于连续标记,^操作符用于取消标记,&操作符用于测试标记【想想位运算便知道原理。】 参考链接
**In cases in which TypeScript determines that an assignment is invalid, but you know that you are dealing with a special case, you can override the type using a type assertion. **【用于告诉类型检测器,该类型可以兼容地转换为断言的类型】
var avenueRoad: House = {
bedrooms: 11,
bathrooms: 10,
butlers: 1
};
// Errors: Cannot convert House to Mansion
var mansion: Mansion = avenueRoad;
// Works
var mansion: Mansion = <Mansion>avenueRoad;Although a type assertion overrides the type as far as the compiler is concerned, there are still checks performed when you assert a type.【但是检测器依然会检测是否可以'强制'转换】
by adding an additional<any>type assertion between the actual type you want to use and the identifier of the variable.【通过在想转换的类型和变量名之间添加类型断言解决】(why?)
var name: string = 'Avenue Road';
// Error: Cannot convert 'string' to 'number'
var bedrooms: number = <number> name;
// Works,bedrooms is alse 'Avenue Road'
var bedrooms: number = <number> <any> name;Traditionally, a single ! is used to invert a statement to reduce nesting in your code, whereas the double !! converts a type to a Boolean.【一般地,单个!用于取反,两个!用于类型转换--其余类型转换为Boolean类型】
The following values are “falsey” and are evaluated as false
• undefined
• null
• false: boolean
• '': string (empty string)
• 0: number
• NaN (the JavaScript Not a Number value)
All other values are evaluated as true. Surprising examples of this include:
• '0': string
• 'False': string
【函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。】
let myAdd: (baseValue:number, increment:number) => number =
function(x: number, y: number): number { return x + y; };myAdd为函数名,baseValue与increment为参数名,这个参数名可以取符合变量名规范的任意值,只要的作用是为了代码的可读性。后面带的number为参数类型,在函数和返回值类型之前使用( =>)符号,箭头符号之后的number为返回值的类型,等号后面的function(x:number,y:number):number{return x+y}为匿名函数,其中有两点需要注意:
-
x与y的名字可以任意取。 -
x与y的number类型声明可以省略。TypeScript中的类型推论可以帮助我们从等号的左边获取到具体的类型。【这种叫“按上下文归类”,是类型推论的一种】
In JavaScript, it is possible to call a function without supplying any arguments, even where the function specifies parameters. It is even possible in JavaScript to pass more arguments than the function requires. In TypeScript, the compiler checks each call and warns you if the arguments fail to match the required parameters in number or type. Because arguments are thoroughly checked, you need to annotate optional parameters to inform the compiler that it is acceptable for an argument to be omitted by calling code. 【在JS中,可以不确定参数个数,但在TypeScript中会检测调用方法的参数个数和类型,所以在不确定参数的时候需要使用Optional Para】
function getAverage(a: number, b: number, c?: number): string{
//do sth.
}
//c is a optional para.Note:
- Optional parameters must be located after any required parameters in the parameter list. For example, the second parameter cannot be optional if the third parameter is required.【optional para 必须放在参数列表最后】
- When you use an optional parameter you must check the value to see if it has been initialized. The typeof check
is the common pattern for this check. 【使用optional para时使用
typeof paraName !=='undefined'检测一下有没有赋初始值】
Default parameters are complementary to optional parameters. Wherever you consider using an optional parameter you should also consider the use of a default parameter as an alternative design.To supply a default value for a parameter, assign a value in the function declaration.【缺省参数与optional para相辅相成,常用于函数参数中赋予默认值,并且是放在普通参数后面】
The JavaScript code generated by default parameters includes a typeof check.【缺省参数也会有类型检测,下例中的beginAt被检测到是number后并不能再赋值字符串】
function concatenate(items: string[], separator = ',', beginAt = 0, endAt = items.length) {
var result = '';
//Error-出错
//beginAt = '0';
for (var i = beginAt; i < endAt; i++) {
result += items[i];
if (i < endAt) {
result += separator;
}
}
return result;
}
var items = ['A', 'B', 'C'];
// 'A,B,C'
var result = concatenate(items);
// 'B-C'you can use a wide range of runtime values as default values.The default value could be calculated , or refer to any variable that could be accessed from within the function body.【TypeScript并不像其他编程语言(例如C#)一样,限制缺省参数的值为常量,可以是运行时才动态获取的变量,例如endAt = items.length】PS:个人认为这是动态语言的优势之一
Rest parameters allow calling code to specify zero or more arguments of the specified type.【剩余参数可以代表0~n个参数】
rest parameters must follow these rules:
-
Only one rest parameter is allowed.【只有一个剩余参数】
-
The rest parameter must appear last in the parameter list.【与可选参数,缺省参数一样,必须在参数最后】
-
The type of a rest parameter must be an array type.【剩余参数是一个数组,可以使用Array.prototype上的方法,与
arguments类数组不同】
To declare a rest parameter, prefix the identifier with three periods and ensure that the type annotation is an array
type【声明缺省参数时,在参数名前加入三个英文句号,并且使用类型数组(默认为any[])进行声明】
In many languages, each overload has its own implementation but in TypeScript the overloads all decorate a single implementation.【TypeScript的实现方法只有1个】
The implementation signature must define parameters and a return value that are compatible with all preceding signatures. As this implies, the return types for each overload can be different and the parameter lists can differ not only in types, but also in number of arguments. 【定义的实现方法的签名必须兼容所有的重载】PS:签名指的是参数的个数,参数的类型和返回值。
When you call a function that has overloads defined, the compiler constructs a list of signatures and attempts to determine the signature that matches the function call. If there are no matching signatures the call results in an error. If one or more signature matches, the earliest of the matching signatures (in the order they appear in the file) determines the return type.【当调用实现的函数时,编译器会构建一个拥有所有重载的签名列表,并且按照重载函数的定义顺序,从上到下匹配确定。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。 】
【TypeScript为什么需要重载?】--对函数调用者友好。当调用的重载的实现函数时,会出现一系列的重载方法,并且签名各不相同,方便调用者根据需求进行调用。
【对调用者友好以及在编译的时候自动检测(check)的强类型语言的特点是TypeScript相对于Javascript的特点】
Specialized overload signatures refer to the ability in TypeScript to create overloads based on string constants. Rather than the overloads being based on different parameters, they are based on the string value of an argument. 【具体化过载签名与过载可以定义不同的参数不同,它基于参数为一个string类型的常量】
class HandlerFactory {
getHandler(type: 'Random'): RandomHandler;
getHandler(type: 'Reversed'): ReversedHandler;
getHandler(type: string): Handler; // non-specialized signature
getHandler(type: string): Handler { // implementation signature
switch (type) {
case 'Random':
return new RandomHandler();
case 'Reversed':
return new ReversedHandler();
default:
return new Handler();
}
}
}There are some rules to follow when using specialized overload signatures【使用具体化过载签名的遵守规则】
• There must be at least one nonspecialized signature.【必须使用一个普通的过载签名】
• Each specialized signature must return a subtype of a nonspecialized signature.【返回类型必须是普通的过载签名的一个子类型(兼容)】
• The implementation signature must be compatible with all signatures.【过载的实现函数必须兼容所有过载声明】
The most common case for specialized signatures is that the nonspecialized signature returns a superclass, with each overload returning a more specialized subclass that inherits (or is structurally compatible with) the superclass.
【最常用的场景是普通过载签名返回超集,而具体化过载签名返回超集的子集。如下:】
// This example does not list all variations...
getElementsByTagName(name: "a"): NodeListOf<HTMLAnchorElement>;
getElementsByTagName(name: "blockquote"): NodeListOf<HTMLQuoteElement>;
getElementsByTagName(name: "body"): NodeListOf<HTMLBodyElement>;
getElementsByTagName(name: "button"): NodeListOf<HTMLButtonElement>;
getElementsByTagName(name: "form"): NodeListOf<HTMLFormElement>;
getElementsByTagName(name: "h1"): NodeListOf<HTMLHeadingElement>;
getElementsByTagName(name: string): NodeList; // Non-specialized signature
getElementsByTagName(name: string): NodeList { // implementation signature
return document.getElementsByTagName(name);
}if you pass a name argument with the value "blockquote", the second signature in Listing 1-31 will be
matched and the return type is NodeListOf<HTMLQuoteElement>.
【上面的例子中可能觉得有具体化过载签名的过载函数定义有点多余,但是这个特性很有用。类似case子句,当调用了过载的实现方法时,传递了"blockquote"实参时,会返回NodeListOf<HTMLQuoteElement>.所以有具体化过载签名的过载函数与没有具体化过载签名的过载函数的关系是特殊与一般】
【功能主要有两个:】
-1. shorthand syntax for defining a function 【简化函数的定义】
//直接写返回值
var addNumbers = (a: number, b: number) => a + b;
//写出函数体
var addNumbers = (a: number, b: number) => {
return a + b;
}
var addNumbers = function (a: number, b: number) {
return a + b;
}
//以上3个函数表达式等同
//如果返回值是object对象,需要用括号包住,否则编译器会错误地认为是一个代码块。eg.
//var makeName = (f: string, l: string) => ({first: f, last: l});-2. preserve the lexical scope of the this keyword 【保护this关键字的词法作用域,解决了以前this的混乱】
var ScopeLosingExample = {
text: "Property from lexical scope",
run: function () {
setTimeout(function() {
alert(this.text);
}, 1000);
}
};
// alerts undefined
//因为setTimeout的this为全局window对象
ScopeLosingExample.run();var ScopePreservingExample = {
text: "Property from lexical scope",
run: function () {
setTimeout(() => {
alert(this.text);
}, 1000);
}
};
// alerts "Property from lexical scope"
//在函数创建的时候就指定了‘this’值,而不是在函数调用的时候。
ScopePreservingExample.run();
//【ps】实际上是一个语法糖,它将ScopePreservingExample函数转换为ScopeLosingExample函数的修正版
//--利用_this保存了外部的this,以防被覆盖。
//如下
var ScopePreservingExample = {
text: "Property from lexical scope",
run: function () {
var _this = this;
setTimeout(function () {
alert(_this.text);
}, 1000);
}
};
// alerts "Property from lexical scope"
ScopePreservingExample.run();**TypeScript interfaces can be used for several purposes. As you would expect, an interface can be used as an abstract type that can be implemented by concrete classes, but they can also be used to define any structure in your TypeScript program. Interfaces are also the building blocks for defining operations that are available in third-party libraries and frameworks that are not written in TypeScript. **【TypeScript中的接口主要有3个:定义具体类的实现(与其他语言的接口类似);定义结构的类型; 用于第三方库的操作定义(Chapter 8)】
Interfaces do not result in any compiled JavaScript code; this is due to type erasure, which is described in Chapter 2. Interfaces are used at design time to provide autocompletion and at compile time to provide type checking. 【TypeScript的接口因为类型擦除,并不会产生额外的JavaScript代码。接口一般用在代码设计阶段的代码自动补全提示和在编译阶段的类型检测】
【类也可以当作接口使用(通过接口继承类,会继承类的成员变量而不用再次声明)】
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};【留坑!TypeScript的自定义接口还可以扩展内建接口】
//接口的定义
interface Vehicle {
// Constructor
new() : Vehicle;
// Properties
currentLocation: Point;
// Methods
travelTo(point: Point);
addPassenger(passenger: Passenger);
removePassenger(passenger: Passenger);
}All classes in TypeScript have a constructor, whether you specify one or not. If you leave out the constructor, the compiler will automatically add one. For a class that doesn’t inherit from another class, the automatic constructor will be parameterless and will initialize any class properties. Where the class extends another class, the automatic constructor will match the superclass signature and will pass arguments to the superclass before initializing any of its own properties.【在TypeSCript的类中至少有一个构造函数,如果没有定义类的构造函数,编译器会为我们自动加上一个。如果没有继承自父类,则编译器主动会初始化该类的无参构造函数以及类的属性;如果继承父类,则编译器自动继承父类的构造参数,然后再初始化类的属性】
class Song {
constructor(private artist: string, private title: string) {
//自动映射赋值,相当于下面的代码。
}
play() {
console.log('Playing ' + this.title + ' by ' + this.artist);
}
}
//If you prefix a constructor parameter with an access modifier, such as private, it will automatically be mapped for you.
//【需要注意:TypeScript的构造函数有`参数属性`的功能,它会自动映射--意味着你可以在类中把构造函数的参数作为类的属性使用】
//等同于以下冗余的代码:
private artist: string;
private title: string;
constructor(artist: string, title: string) {
this.artist = artist;
this.title = title;
}【在TypeScript中类的成员变量默认是public,可以自由地被类的外部访问。private只能是在类的内部,而protected则是可以被派生类继承并且使用】
【你可以使用readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化】
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.【在类的方法前面使用get/set修饰】
class Employee {
private _fullName: string;
//访问器
get fullName(): string {
return this._fullName;
}
//修改器
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}【在成员变量(属性或者函数)名前面使用static来修饰,与Java的用法一样】
There are two types of class heritage in TypeScript. A class can implement an interface using the implements keyword and a class can inherit from another class using the extends keyword.【在TypeScript中有两种继承方式,一种是继承(实现)接口,一种是继承父类】
If you do specify the interface using the implements keyword, your class will be checked to ensure that it complies with the contract promised by the interface. 【在实现接口达到继承的目的时,类会被检测,确定它符合接口的规定--比如要具体实现接口的方法,实现方法的签名必须兼容接口方法的签名。虽然实现方法的参数可以少于接口方法的参数,这样类的方法会忽略该部分参数,但是匹配的参数必须类型兼容!】
A class can implement multiple interfaces, with each interface being separated by a comma, for example: implements Audio, Video. 【可以利用接口达到多继承的目的,多个接口名用逗号隔开】
If you call a class method from an event, or use it as a callback, the original context of the method can be lost, which results in problems using instance methods and instance properties. When the context is changed, the value of the this keyword is lost.【如果从事件中调用类的成员方法,或者将类的成员方法作为回调函数,成员方法原本的上下文会被改变,this关键字的指向可能不会和预期的一样】
【有如下的办法解决这种情况,可以保护好this的上下文】
【当不知道何时何地当作回调函数或者事件被调用时,把箭头函数作为类的属性】
class ClickCounter {
private count = 0;
registerClick = () => {
this.count++;
alert(this.count);
}
}【在回调函数处,使用函数包含住类的成员方法,使得this正确指向--看下面的区别】
//将registerClick成员方法作为函数直接赋给onclick函数,当调用的时候,registerClick是被当成函数被调用。this指向window全局对象
document.getElementById('target').onclick = clickCounter.registerClick;
//将匿名函数赋给onclick函数,当调用的时候,实际上是调用clickCounter对象的registerClick方法。this指向调用的clickCounter对象
document.getElementById('target').onclick = function () {
clickCounter.registerClick();
};【ES5引入了bind方法来设置函数的this值,而不用考虑函数如何被调用的。】
var clickHandler = clickCounter.registerClick.bind(clickCounter);
document.getElementById('target').onclick = clickHandler;【用来判断一个对象是否是该类或者(原型链上的类)的实例】
class Display {
name: string = '';
fooName:string;
}
class Television extends Display {
}
class HiFi {
}
var display = new Display();
var television = new Television();
var hiFi = new HiFi();
var isDisplay;
// true
isDisplay = display instanceof Display;
// true (inherits from Display)
isDisplay = television instanceof Display;
// false
isDisplay = hiFi instanceof Display;【用来判断变量是否是该类的属性或者原型链的属性】
//还是上面的例子
var hasName;
// true
hasName = 'name' in display;
// true
hasName = 'name' in television;
//false--未初始化
hsaFooName = 'fooName' in television;
// true
hasName = 'name' in hiFi;It is important to note that due to the code generation in the TypeScript compiler, an uninitialized property will not be detected because unless the property has a value, it does not appear in the compiled JavaScript code. 【需要注意的是,对于未初始化的属性,不会被编译成js代码,也就不会存在该属性!】
【typeof并不能获取真正的类型名,比如typeof null的结果为'object',其他的自定义对象也都是'object'。但是我们可以使用正则表达式来解析一个对象的constructor构造函数】
class Describer {
static getName(inputClass) {
// RegEx to get the class name
var funcNameRegex = /function (.{1,})\(/;
var results = (funcNameRegex).exec((<any> inputClass).constructor.toString());
return (results && results.length > 1) ? results[1] : '';
}
}
var tv = new Television();
var radio = new HiFi();
var tvType = Describer.getName(tv); // Television
var radioType = Describer.getName(radio); // HiFi【主要是为了解决命名冲突和依赖管理】
【关于前端的模块化开发历程可见--http://chaoskeh.com/blog/why-seajs.html (命名空间与模块化) 以及 seajs/seajs#588 (CommanJS与AMD)】
【常见的Node使用的是CommonJS模块规范(服务器端),AMD规范的实现有RequireJS(浏览器端),而ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的 依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。】
【CommonJS和AMD的一些区别】
//CommonJS--主要用于服务器端的模块规范 【Modules/1.0】
//content.js--定义并暴露接口
module.exports = 'A cat'
//index.js
// 执行到此处时,a.js 才同步下载并执行
var animal = require('./content.js');
//AMD:--主要用于浏览器端的模块规范【Modules/Async 流派】
//content.js--定义
// 在这里(define的时候),模块 a 已经下载并执行好
define('content.js', function(){
return 'A cat';
})
//index.js--使用
// 此处仅仅是取模块 a 的 exports
//AMD 里提前下载 a.js 是浏览器的限制,没办法做到同步下载。AMD也提前执行了...
require(['./content.js'], function(animal){
console.log(animal); //A cat
})
})to be continued..