《零》,概述javascript
※,vscode 的智能提示用的是 TypeScript language service,这个服务有个叫 AutoAutomatic Type Acquisition(ATA)的东西,ATA会根据package.json中列出的npm modules拉取这些模块的类型声明文件(npm Type Declaration files,即 *.d.ts 文件),从而给出智能提示。html
※,安装TypeScript 编译器: npm install -g typescript(全局安装) 或 npm install --save-dev typesript (仅将ts编译器安装在当前项目中)。若是想装特定版本的Ts可使用npm install -g typescript@3.3.1java
※,tsc -h 查看帮助文档。各类编译选项参数和其含义。git
※,tsc --init 能够生成一个tsconfig.json配置文件(固然也能够手动建立此文件)。此文件配置了TypeScript项目信息,如编译选项, 包含的文件等等。没有此文件则使用默认设置。使用tsc命令编译ts文件时,ts编译器会自动查找 tsconfig.json,并按照其中的配置编译ts文件。程序员
※,tsc xxx.ts用于编译某个ts文件,也能够直接运行tsc命令,编译全部的ts文件。若是有tsconfig.json文件,直接运行tsc将会编译tsconfig.json中包含的全部ts文件。github
※,tsconfig.json中的一些配置项说明:typescript
{
"compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "out",//此属性配置了编译后js文件存储的位置
"sourceMap":true, //true表示编译后同时生成map文件,指示ts和js文件的对应关系。
},
"files":{
},
"extends":{
},
.....
}※,npm
《一》,文档学习 https://www.tslang.cn/docs/home.html json
一,快速入门数组
1,要注意的是尽管 ts 中有错误(类型不匹配等),相应的js
文件仍是被建立了。 就算你的代码里有错误,你仍然可使用TypeScript。但在这种状况下,TypeScript会警告你代码可能不会按预期执行。
2,在TypeScript里,只要两个类型内部的结构兼容那么这两个类型就是兼容的。 这就容许咱们在实现接口时候只要保证包含了接口要求的结构就能够(实现时能够比接口定义的字段多,可是不能比其少),而没必要明确地使用 implements
语句。固然写上implements语句更清晰
3,类:它带有一个构造函数和一些公共字段,要注意的是,在构造函数的参数上使用public等同于建立了同名的成员变量。
二,基础类型:
1,数字:和JavaScript同样,TypeScript里的全部数字都是浮点数。 这些浮点数的类型是 number
。
2,字符串:可使用模版字符串,它能够定义多行文本和内嵌表达式。 这种字符串是被反引号包围( `
),而且以${ expr }
这种形式嵌入表达式。如 `my name is ${myName}, I am ${age+11} years old`
2.1,JavaScript 中的String与string的区别:String是构造函数,而"string"是变量的一种类型.
JS的数据类型通常用大写的String,Number,Undefined,Null,Object来表示,而小写的类型的做用仅仅是在使用 typeof 和 instanceof 用来判断具体类型,或是做为返回的字符串,用来代表该类型是什么,是基本类型仍是引用类型,其余地方就用不到了。 https://blog.csdn.net/fengwei4618/article/details/77955261
typeof String // "function" typeof string // "undefined" typeof "string" // "string"
3,数组:
TypeScript像JavaScript同样能够操做数组元素。 有两种方式能够定义数组。 第一种,能够在元素类型后面接上 []
,表示由此类型元素组成的一个数组:
let list: number[] = [1, 2, 3];
第二种方式是使用数组泛型,Array<元素类型>
:
let list: Array<number> = [1, 2, 3];
4,元组 Tuple
元组类型容许表示一个已知元素数量和类型的数组,各元素的类型没必要相同。 好比,你能够定义一对值分别为 string
和number
类型的元组。
// Declare a tuple type let x: [string, number]; // Initialize it x = ['hello', 10]; // OK // Initialize it incorrectly x = [10, 'hello']; // Error
当访问一个已知索引的元素,会获得正确的类型:
console.log(x[0].substr(1)); // OK console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
当访问一个越界的元素,会使用联合类型替代:
x[3] = 'world'; // OK, 字符串能够赋值给(string | number)类型 console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString x[6] = true; // Error, 布尔不是(string | number)类型
联合类型是高级主题,咱们会在之后的章节里讨论它。
5,枚举
6,Any
7,Void
8,Null 和Undefined: Null是没有在内存中开辟空间,Undefined是开辟了空间可是里面没有存值。
9,Never
10,Object:object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
以外的类型。
declare function create(o: object | null): void; create({ prop: 0 }); // OK create(null); // OK create(42); // Error create("string"); // Error create(false); // Error create(undefined); // Error
11,类型断言
类型断言比如其它语言里的类型转换。
类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
另外一个为as
语法:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
两种形式是等价的
三,变量声明
先猜一下下面的代码会输出什么结果?
for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); } 结果是打印出10个10!解释以下:setTimeout在若干毫秒后执行一个函数而且是在for循环结束后。for循环结束后,i的值为10.因此当函数被调用的时候,它会打印出10! 若是想打印出1~9,有两个方法,1,将var变成let关键词(ES6才开始支持)或者 2,使用当即执行函数捕捉每次循环的i的值。代码以下: for (var i = 0; i < 10; i++) { (function(j){ setTimeout(function () { console.log(j); }, j * 10); })(i); }
1,关于变量做用域的一些说明:在ES6以前只有全局做用域和函数做用域,ES6新增了块级做用域。 每次JavaScript引擎进入到一个做用域后(对于var声明的变量,这个做用域是函数做用域;对于let声明的变量,这个做用域是块级做用域----if块,for循环块等等),它都会建立一个 变量 的环境。就算做用域内代码已经执行完毕,这个环境与引擎 捕获的变量依然存在。JavaScript引擎的资源回收机制 会在合适的时机本身回收 这个环境和 变量。当let声明出如今循环体中时(即有了块级做用域的概念),不只是在循环里引入了一个新的变量环境,并且针对每次迭代都会建立这样一个新做用域。这就是咱们使用当即执行函数表达式作的事情。
2,const声明 是声明变量的另外一种方式。const声明与let声明有相同的做用域规则(块级做用域),和let的区别是 const声明的变量在赋值后不能再被改变。注意一点:若是用const声明一个引用类型,好比对象 const o = {name:"xx", age:12}, 声明后能够改变变量o的内部状态,如设置 o.name = "yyy"是被容许的。
3, 使用var能够重复声明一个变量,最终你只会获得一个。 可是使用let声明变量 在一个做用域内只能声明一个同名变量。见下面的例子:
例一: let a = 3; if (true) { let a = 4;//块级做用域中声明的变量只在 这个块级 起做用。 } console.log(a);//结果输出是3 例二: function f(condition, x) { if (condition) { let x = 100;//块级做用域中声明的变量只在此块中有意义,此块中的x覆盖了外面做用域的x. return x; } return x; } console.log(f(true, 0));//输出100 console.log(f(false, 0));//输出0
另外,若是块级做用域里用到了一个变量,可是在块里没有此变量的声明,则引擎会继续向上找此块所在的函数(或更高一级的块等),若是尚未引擎继续向上找,若是一直找不到就会报错
4,解构赋值
let [first, second] = [3,4];//first=3,second=4; [second, first] = [first, second];//first=4,second=3; let [first] = [1,2,3,4];//first = 1; let [,first, second] = [1,2,3,4];//first=2,second=3; let [,first,,second] = [1,2,3,4];//first=2,second=4; let [first,,second] = [[21,22,23],3]//first=[21,22,23],second=undefined let [first];//报错 let first;//first=undefined let [first,second]=[3];//first=3,second=undefined //用 ... 语法建立剩余变量。 let [first, ...rest] = [1,2,3,4,6];//first=1,rest=[2,3,4,6];
[first, second] = [11, 22];//注意first,second没有声明过,first=11,second=22;
let o = { c: "foo", b: 12, a: "bar" }; let {a, b, d} = o;//a=bar, b=12, d=undefined; //用 ... 语法建立剩余变量 let { a, ...rest } = o;//a=bar,rest={ c: 'foo', b: 12 }; /* * 就像数组解构,可使用未声明的赋值,注意要将整个赋值用()括起来, * 由于JavaScript一般会将以 { 开头的语句解析为一个块。 */ ({ e, f } = { e: "contemplate", f: "思考, 沉思" });//e=contemlate,f="思考, 沉思"
5,展开Spread: 展开和解构是相反的操做。
let first = [1, 2]; let second = [3, 4]; let obj = { a: "aaa", b: "bbb" }; let spread = [...first, ...second, { ...obj, a: "ambiance" }]; let spread2 = [...first, ...second, { a: "ambiance", ...obj }] //注意,展开对象时,同名的key,后面的将覆盖前面的。 console.log(spread);//[ 1, 2, 3, 4, { a: 'ambiance', b: 'bbb' } ] console.log(spread2);//[ 1, 2, 3, 4, { a: 'aaa', b: 'bbb' } ] //展开是浅拷贝(shallow copy),不会改变原变量 console.log(first);//[1,2]; //展开对象时,它只能展开对象 自身的可枚举属性,不可枚举属性以及方法就不能展开了 class C { p = 12; m() { } } let c = new C(); let clone = { ...c }; clone.p; // ok clone.m(); // error!
四,接口
0,若是一个变量定义为某个接口定义的类型,那么
1,可选属性
interface SquareConfig { color?: string; width?: number; }
2,只读属性
※,TypeScript 具备ReadOnlyArray<T>,它与Array<T>
类似,只是把全部可变方法去掉了,所以能够确保数组建立后不再能被修改:
let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = a; ro[0] = 12; // error! ro.push(5); // error! ro.length = 100; // error! a = ro; // error!
上面代码的最后一行,能够看到就算把整个ReadonlyArray
赋值到一个普通数组也是不能够的。 可是你能够用类型断言重写:
a = ro as number[];
※,readonly
vs const
最简单判断该用readonly
仍是const
的方法是看要把它作为变量使用仍是作为一个属性。 作为变量使用的话用 const
,若作为属性则使用readonly
。
3,额外的属性检查
TypeScript中的对象字面量会被特殊对待:当将它们赋值给变量或做为参数传递的时候,它们会通过 额外属性检查。若是一个对象字面量存在任何“目标类型”不包含的属性时,你会获得一个错误。
interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): { color: string; area: number } { // ... } //注意,colour不是color,这里会获得一个错误:SquareConfig接口里不存在colour属性。 let mySquare = createSquare({ colour: "red", width: 100 });
绕开这些额外的属性检查有如下几种方法:
※,使用类型断言
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
※,添加一个字符串索引签名(后面会讲)
interface SquareConfig { color?: string; width?: number; [propName: string]: any; } 这里索引签名表示的是SquareConfig能够有任意数量的属性,而且只要它们不是color和width,那么就无所谓它们的类型是什么。
※,将对象字面量赋值给一个变量,由于变量不会像对象字面量同样会通过额外的属性检查。
let squareOptions = { colour: "red", width: 100 }; let mySquare = createSquare(squareOptions);
※,
4,函数类型
※,接口可以描述JavaScript中对象拥有的各类各样的外形。 除了描述带有属性的普通对象外,接口也能够描述函数类型。为了使用接口表示函数类型,咱们须要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每一个参数都须要名字和类型。
//函数类型接口定义了一个函数的各参数类型以及返回值类型 interface SearchFunc { (source: string, substring: string): boolean; } let mySearch: SearchFunc; //函数参数名称能够和接口中不一致 mySearch = function (src: string, sub: string): boolean { return src.search(sub) > -1; } //也能够不指定函数参数和返回值的类型,TypeScript的类型系统会本身推断出参数和返回值类型 mySearch = function (src, sub) { return src.search(sub) > -1; }
※,
5,可索引的类型(索引签名)
※,和描述函数类型差很少,TypeScript还能够用接口描述那些 “经过索引获得” 的类型,好比a[10], ageMap['Evan']等。接口中定义了一个索引签名,描述了索引的类型(如10,"Evan"的类型)以及索引的返回值类型(如a[10],ageMap["Evan"]的类型)。
//定义一个StringArray接口,它具备一个索引签名(用中括号括起来的这个形式的就是索引签名)。这个索引签名表示当使用一个number类型索引StringArray时会获得string类型的返回值。 interface StringArray { [index: number]: string; } let myArray: StringArray; myArray = ["Bob", "Fred"]; let myStr: string = myArray[0];
※,TypeScript支持两种索引签名:字符串索引和数字索引。 能够同时使用两种类型的索引,可是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是由于当使用 number
来索引时,JavaScript会将它转换成string
而后再去索引对象。 也就是说用 100
(一个number
)去索引等同于使用"100"
(一个string
)去索引,所以二者须要保持一致。
class Animal { name: string; } class Dog extends Animal { breed: string; } // 错误:使用数值型的字符串索引,有时会获得彻底不一样的Animal! interface NotOkay { [x: number]: Animal; [x: string]: Dog; } let h:NotOkay = {}; h[100] = new Animal(); h["100"] = new Dog(); console.log(h[100]);//本想获得Animal{},输出的倒是Dog{}
※,一个例子:
interface NumberDictionary { //这个是类型的索引签名,索引是string类型,索引返回值是number类型。 [index: string]: number; length: number; // 能够,length是索引string类型,其索引后的返回值是number类型 name: string // 错误,`name`的类型与索引类型返回值的类型不匹配 }
※, 将索引签名设置为只读,这样就防止了给索引赋值:
interface ReadonlyStringArray { readonly [index: number]: string; } let myArray: ReadonlyStringArray = ["Alice", "Bob"]; myArray[2] = "Mallory"; // error!
你不能设置myArray[2]
,由于索引签名是只读的
6,类类型
※,类能够用关键字implements实现一个接口,实现接口时,接口里的属性或方法类中必须有,也能够定义接口中不存在的属性或方法。便可以多不能够少。
※,类静态部分与实例部分的区别(有些难理解)
当你操做类和接口的时候,你要知道类是具备两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会获得一个错误:
interface ClockConstructor { new (hour: number, minute: number); } class Clock implements ClockConstructor { currentTime: Date; constructor(h: number, m: number) { } }
这里由于当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,因此不在检查的范围内。
所以,咱们应该直接操做类的静态部分。 看下面的例子,咱们定义了两个接口, ClockConstructor
为构造函数所用和ClockInterface
为实例方法所用。 为了方便咱们定义一个构造函数 createClock
,它用传入的类型建立实例。
interface ClockConstructor { new (hour: number, minute: number): ClockInterface; } interface ClockInterface { tick(); } function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface { return new ctor(hour, minute); } class DigitalClock implements ClockInterface { constructor(h: number, m: number) { } tick() { console.log("beep beep"); } } class AnalogClock implements ClockInterface { constructor(h: number, m: number) { } tick() { console.log("tick tock"); } } let digital = createClock(DigitalClock, 12, 17); let analog = createClock(AnalogClock, 7, 32);
由于createClock
的第一个参数是ClockConstructor
类型,在createClock(AnalogClock, 7, 32)
里,会检查AnalogClock
是否符合构造函数签名。
7,继承接口
※,一个接口能够继承多个接口。
8,混合类型 (有点绕)
※,
9,接口继承类
※,当接口继承了一个类类型时,它会继承类的成员(即属性和方法)但不包括其实现。 就好像接口声明了全部类中存在的成员,但并无提供具体实现同样。 接口一样会继承到类的private和protected成员。 这意味着当你建立了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
五,类
1,继承
※,和Java同样,TypeScript不容许多继承。一个类只能继承一个类。
※, 父类也叫基类,超类。子类也叫派生类。
※,子类继承了父类,若是子类中没有构造函数则会自动执行父类的构造函数。若是子类 中有构造函数,则它 必须 调用 super(parameters)
,它会执行基类的构造函数。 并且,在构造函数里访问 this
的属性以前,咱们 必定 要调用 super(parameters)
。 这个是TypeScript强制执行的一条重要规则。
※,访问修饰符(public ,protected, private):public 修饰符能够省略。protected修饰的属性和方法能够在声明其的类内部 以及 继承了这个类的子类内部用this调用。实例化后不能调用此属性或方法。 private修饰的属性或方法只能在声明其的类内部用this调用。若是一个类的构造函数被标记为protected,那么这个类不能被实例化,可是能够被继承。若是一个类的构造函数被标记为private,那么这个类不能被实例化,也不能被继承。
TypeScript使用的是结构性类型系统。 当咱们比较两种不一样的类型时,并不在意它们从何处而来,若是全部成员的类型都是兼容的,咱们就认为它们的类型是兼容的。
然而,当咱们比较带有 private
或 protected
成员的类型的时候,状况就不一样了。 若是其中一个类型里包含一个private
成员,那么只有当另一个类型中也存在这样一个 private
成员, 而且它们都是来自同一处声明时,咱们才认为这两个类型是兼容的。 对于 protected
成员也使用这个规则。
下面来看一个例子,更好地说明了这一点:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } class Rhino extends Animal { constructor() { super("Rhino"); } } class Employee { private name: string; constructor(theName: string) { this.name = theName; } } let animal = new Animal("Goat"); let rhino = new Rhino(); let employee = new Employee("Bob"); animal = rhino; animal = employee; // 错误: Animal 与 Employee 不兼容.
※,可使用readonly修饰符修饰类的属性,将其标记为只读属性。readonly不能修饰类的方法。
※,
2,参数属性
※,若是一个类的构造函数的参数有public, protected,private修饰符,那么就至关于类定义了一个同名属性,同时构造函数里给其赋了值。只有readonly修饰参数也能够,至关于public readonly xxx.
class Person { constructor(public name: string) { } } //以上类等同于以下 class Person { public name: string; constructor(name: string) { this.name = name; } }
※,
3,存取器(get和set方法)
※,类中的属性能够经过getters 和 setters 来控制对此属性的访问。好比,设置一个属性的值时,若是须要知足必定的条件才能设置,那么此时能够经过getters/setters。只带有get方法而不带有set方法的属性被自动推断为readonly属性。存取器的例子以下:
let passcode = "secret passcode"; 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!"); } } } let employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
※,
4,静态属性
※,TypeScript的静态属性或方法只能经过类名访问。没有self之类的东西
※,
5,抽象类
※,抽象类做为其它派生类的基类使用。抽象类不能被实例化。不一样于接口,抽象类能够包含成员的实现细节。
※,抽象类中能够定义抽象方法(抽象方法只能出如今抽象类中,普通类中不能定义抽象方法),抽象方法和接口语法差很少,只有方法签名,没有实现。可是和接口不同的是,抽象方法必须有关键字abstract,实际至关于public abstract,也能够是protected abstract,可是不能是private abstract,由于抽象方法必须被派生类实现(可是抽象类中的非抽象方法不须要必定被派生类实现)。
※,
6,
7,
8,
9,
六,函数
在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义 行为的地方。 TypeScript为JavaScript函数添加了额外的功能,让咱们能够更容易地使用。
1,函数的类型定义:
※,返回值类型:TypeScript可以根据返回语句自动推断出返回值类型,所以咱们一般省略它。
※,函数类型:若是定义一个变量为函数类型,其完整的写法是:
let myAdd: (baseValue: number, increment: number) => number = function(x: number, y: number): number { return x + y; }; 还有一种写法,用带有调用签名的对象字面量来讲明函数类型, //除了使用 (x:string)=>string,还能够以下: let sayHello: { (x: string): string } = function (theName: string): string { console.log("hello " + theName); return "hello " + theName; }; console.log(sayHello); sayHello("Evan")
※,类型推断:若是你在赋值语句的一边指定了类型可是另外一边没有类型的话,TypeScript编译器会自动识别出类型:
// TypeScript根据右边值得类型推断变量myAdd的完整类型。 let myAdd = function(x: number, y: number): number { return x + y; }; // TypeScript根据myAdd的类型推断变量x,y的类型为number,其返回值也是number let myAdd: (baseValue: number, increment: number) => number = function(x, y) { return x + y; }; //甚至还能够这么写(不推荐)。TypeScript会根据myAdd的类型推断出函数有两个number类型的参数,调用myAdd时若是参数个数或类型不对会报错,可是这个样子声明这个函数时是不会报错的。 let myAdd: (baseValue: number, increment: number) => number = function () {return 33;}
※,
2,可选参数和默认参数 (ps,Java中不支持默认参数,Java解决此问题的方法是方法重载!)
※,TypeScript的函数,调用时传入的参数个数和声明时的参数个数要保持一致(JavaScript中没有硬性要求)。
※,可选参数:在函数声明时,参数后面加个 “?”可代表此参数是可选的。注意,可选参数 必须放在 必选参数 后面。
※,默认参数:在函数声明时,参数能够初始化一个默认值,当调用时没有传入参数或传了一个undefined(注意,传null时此参数值为null而不使用默认值)时,此参数默认使用默认值。默认参数没必要在必须参数后面。若是带默认值的参数出如今必须参数以前,则调用时必须明确的传入undefined值来获取默认值。
※,可选参数 和 放在必须参数后面的默认参数 共享参数类型:(注意是有条件的,若是默认参数是在必须参数的前面,此时默认参数是必需要传的,则和可选参数再也不共享参数类型)
function buildName(firstName: string, lastName?: string) { // ... }
和
function buildName(firstName: string, lastName = "Smith") { // ... }
共享一样的类型(firstName: string, lastName?: string) => string
。 默认参数的默认值消失了,只保留了它是一个可选参数的信息。
※,
3,剩余参数
※,JavaScript里,可使用 arguments 来访问全部传入的参数。在TypeScript里,可使用剩余参数将全部参数收集到一个变量里。剩余参数会被当作个数不限的可选参数。 能够一个都没有,一样也能够有任意个。 编译器建立参数数组,名字是你在省略号( ...
)后面给定的名字,你能够在函数体内使用这个数组。
这个省略号也会在带有剩余参数的函数类型定义上使用到:
function buildName(firstName: string, ...restOfName: string[]) { return firstName + " " + restOfName.join(" "); } let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
※,
4,this (函数中的this)
※,JavaScript里,this的值是在函数被调用的时候才肯定的。这是个既强大又灵活的特色,可是须要程序员弄清楚函数调用的上下文,而这并非一件简单的事情。看一个例子:
let deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), createCardPicker: function() { return function() { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); console.log("card: " + pickedCard.card + " of " + pickedCard.suit); ※,运行后发现会报错!调用cardPicker()的时候报错了,由于这里只是独立的调用了cardPicker(),顶级的非方法式调用会将this视为window对象(注意:严格模式下,this为undefined而不是window对象), ※,JavaScript(ECMAScript 6或叫ECMAScript 2015以前)解决这个问题的方法有两个:bind() 和 call()/apply(),使用以下: 1,call() let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker.call(deck);//指定函数cardPicker在deck的做用域上调用。 2,bind() let cardPicker = deck.createCardPicker(); let newCardPicker= cardPicker.bind(deck);//bind()返回一个改变了做用域的函数。 let pickedCard = newCardPicker(); 以上两种方式均可以获得预想中的结果。
※,this
和 箭头函数(ECMAScript 6中的语法):
基础:各类特殊形式的箭头函数,好比a=>a, ()=>a, a=>({foo:"bar"})//单语句返回一个对象时,不能直接用{},要用()括起来。
进阶[很是很是很是好的解析了箭头函数中的this的通常性规则]
【本身总结】箭头函数并不绑定 this,arguments,super(ES6),抑或 new.target(ES6)。在箭头函数上使用call()
或者apply()
调用箭头函数时,没法对this
进行绑定,即传入的第一个参数被忽略。箭头函数中的this的值 确切来讲就是整个箭头函数所在位置 处的那个地方的this值。
//例一 function foo() { setTimeout(() => { console.log(this); console.log("id:", this.id); }, 100); } /** * 解析:整个箭头函数在setTimeout()的参数里,这里的this就是foo函数做用域的this,也就是谁调foo函数,this就是谁 */ foo();//this是window对象,this.id:undefined foo.call({id:33});//this是传入的对象{id:33},this.id:33.注意这里是对foo应用call()而不是对箭头函数应用call() //例二: let test = () => { console.log(this); console.log(this.age); } /** * 解析:整个箭头函数就在全局的做用域里,箭头函数中的this就是全局做用域的this,即window对象 */ test.call({ age: 333 });//对箭头函数应用call()没法绑定this,即传入的对象会被忽略。this是window对象,this.age:undefined
在ECMAScript 以及 TypeScript中,可使用箭头函数 使函数被返回时就已经绑定好正确的this值。箭头函数能保存函数建立时的this值而不是调用时的值。【箭头函数的this始终指向函数定义时的this,而非执行时】。
let deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), createCardPicker: function() { // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here。下面的箭头函数能够在这里就捕获到this的值,而不是调用时才肯定this的值。 return () => { let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); alert("card: " + pickedCard.card + " of " + pickedCard.suit);
※,给函数加上虚假的this参数:
能够给函数加上一个this参数,这个参数是虚假的,必须放在全部参数的最前面。这个参数不会影响其余的参数,也不算作参数的个数。这么作的做用是能够指出this的类型,明确指出这个函数只能在this对应的对象上调用。
interface Person { //第一个this不算参数,这个方法须要一个参数 getName(this:Person, theName:string):string; } let p1: Person = { //这里第一个参数也能够写为 this:Person,而且推荐这么写,由于能够明确指出这个函数只能在Person对象上调用。 getName: function (theName:string) { console.log(this); return theName; } } //调用时只须要一个参数便可。 p1.getName("Evan Tong");
※,TypeScript中的函数重载。
JavaScript中函数的签名(名字,参数个数,参数类型等)很灵活也很随意,可是在TypeScript这种类型系统的语言中,这种作法就不太好了,好比调用时没法知晓具体的参数个数即类型。TypeScript支持函数重载。方法以下:
let suits = ["hearts", "spades", "clubs", "diamonds"]; /** * 重载列表位置必须放在具体的实现的函数以前。之间不容许写任何其余代码。 * 这里重载列表是是前2个,第三个不是重载列表的一部分。 * 重载后pickCard函数在调用的时候会进行正确的类型检查,参数是一个对象或一个数字,以其余参数调用pickCard都会产生错误 * 为了让编译器可以选择正确的检查类型,它与JavaScript里的处理流程类似。 它查找重载列表,尝试使用第一个重载定义。 * 若是匹配的话就使用这个。 所以,在定义重载的时候,必定要把最精确的定义放在最前面。 */ function pickCard(x: { suit: string; card: number; }[]): number; function pickCard(x: number): { suit: string; card: number; }; function pickCard(x): any { // Check to see if we're working with an object/array // if so, they gave us the deck and we'll pick the card if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // Otherwise just let them pick the card else if (typeof x == "number") { let pickedSuit = Math.floor(x / 13); return { suit: suits[pickedSuit], card: x % 13 }; } } let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }]; let pickedCard1 = myDeck[pickCard(myDeck)]; console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit); let pickedCard2 = pickCard(15); console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);
※,
七,泛型 (Generics)
1,泛型之Hello World
/* * T是类型变量,它是一种特殊的变量,只用于表示类型而不是值。能够理解为函数接受两种参数:类型参数T(用中括号括起来) 和 参数arg。这个函数叫作泛型函数 */ function identity<T>(arg: T): T { return arg; }
function loggingIdentity<T>(arg: T[]): T[] { console.log(arg.length); // Array has a .length, so no more error return arg; }
你能够这样理解loggingIdentity
的类型:泛型函数loggingIdentity
,接收类型参数T
和参数arg
,它是个元素类型是T
的数组,并返回元素类型是T
的数组。 若是咱们传入数字数组,将返回一个数字数组,由于此时 T
的的类型为number
。 这可让咱们把泛型变量T当作类型的一部分使用,而不是整个类型,增长了灵活性。
咱们也能够这样实现上面的例子:
function loggingIdentity<T>(arg: Array<T>): Array<T> { console.log(arg.length); // Array has a .length, so no more error return arg; }
2,泛型函数的类型
泛型函数的类型与非泛型函数的类型没什么不一样,只是有一个类型参数在最前面,像函数声明同样:
function identity<T>(arg: T): T { return arg; } let myIdentity: <T>(arg: T) => T = identity;
咱们也可使用不一样的泛型参数名,只要在数量上和使用方式上能对应上就能够。
function identity<T>(arg: T): T { return arg; } let myIdentity: <U>(arg: U) => U = identity;
咱们还可使用带有调用签名的对象字面量来定义泛型函数:
function identity<T>(arg: T): T { return arg; } let myIdentity: {<T>(arg: T): T} = identity;
这引导咱们去写第一个泛型接口了。 咱们把上面例子里的对象字面量拿出来作为一个接口:
interface GenericIdentityFn { <T>(arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn = identity;
一个类似的例子,咱们可能想把泛型参数看成整个接口的一个参数。 这样咱们就能清楚的知道使用的具体是哪一个泛型类型(好比: Dictionary<string>而不仅是Dictionary
)。 这样接口里的其它成员也能知道这个参数的类型了。
interface GenericIdentityFn<T> { (arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn<number> = identity;
注意,咱们的示例作了少量改动。 再也不描述泛型函数,而是把非泛型函数签名做为泛型类型一部分。 当咱们使用GenericIdentityFn
的时候,还得传入一个类型参数来指定泛型类型(这里是:number
),锁定了以后代码里使用的类型。 对于描述哪部分类型属于泛型部分来讲,理解什么时候把参数放在调用签名里和什么时候放在接口上是颇有帮助的。
除了泛型接口,咱们还能够建立泛型类。 注意,没法建立泛型枚举和泛型命名空间。
※,
3,泛型类
4,泛型约束
※,
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); // Now we know it has a .length property, so no more error return arg; }
※,你能够声明一个类型参数,且它被另外一个类型参数所约束。 好比,如今咱们想要用属性名从对象里获取这个属性。 而且咱们想要确保这个属性存在于对象 obj
上,所以咱们须要在这两个类型之间使用约束。
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); // okay getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
※,
5,在泛型里使用类类型
6,
八,枚举(Enums)
1,TypeScript支持两种枚举:数字的和基于字符串的枚举。
※,字符串枚举必须给每一个枚举成员一个初始化的值。
※,每一个枚举成员都有一个值,值能够分为两种类型:计算出来的 和 常量的。
※,数字枚举 有反向映射机制(即根据枚举属性名能够获得枚举成员的值,也能够反过来,根据枚举成员的值获得枚举成员的名称。),而字符串枚举没有反向映射。
※,
2,常量枚举(const enums)
※,常量枚举经过在枚举上使用 const
修饰符来定义。
※,常量枚举成员的值只能是常量的,不能是计算出来的。不一样于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之因此能够这么作是由于,常量枚举不容许包含计算成员。
const enum Directions { Up, Down, Left, Right } let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
生成后的代码为:
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
※,
3,
九,类型兼容性
1,介绍
TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义(nominal)类型造成对比。(译者注:在基于名义类型的类型系统中,数据类型的兼容性或等价性是经过明确的声明和/或类型的名称来决定的。这与结构性类型系统不一样,它是基于类型的组成结构,且不要求明确地声明。) 看下面的例子:
interface Named { name: string; } class Person { name: string; } let p: Named; // OK, because of structural typing p = new Person();
在使用基于名义类型的语言,好比C#或Java中,这段代码会报错,由于Person类没有明确说明其实现了Named接口。
TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计的。 由于JavaScript里普遍地使用匿名对象,例如函数表达式和对象字面量,因此使用结构类型系统来描述这些类型比使用名义类型系统更好。
2,比较两个函数的兼容
3,枚举的兼容性
※,枚举类型与数字类型兼容,而且数字类型与枚举类型兼容。不一样枚举类型之间是不兼容的。好比,
enum Status { Ready, Waiting }; enum Color { Red, Blue, Green }; let status = Status.Ready; status = Color.Green; // Error
※,
4,类的兼容性
※,类与对象字面量和接口差很少,但有一点不一样:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数(属于类的静态部分)不在比较的范围内。
class Animal { feet: number; constructor(name: string, numFeet: number) { } } class Size { feet: number; constructor(numFeet: number) { } } let a: Animal; let s: Size; a = s; // OK s = a; // OK
※,类的私有成员和受保护成员
类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时,若是目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 一样地,这条规则也适用于包含受保护成员实例的类型检查。 这容许子类赋值给父类,可是不能赋值给其它有一样类型的类。
※,
5,泛型的兼容性
6,在TypeScript里,有两种兼容性:子类型和赋值。 它们的不一样点在于,赋值扩展了子类型兼容性,增长了一些规则,容许和any
来回赋值,以及enum
和对应数字值之间的来回赋值。
语言里的不一样地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的,即便在implements
和extends
语句也不例外。
更多信息,请参阅TypeScript语言规范.
7,
十,高级类型 (对比基础类型:string,number,数组,null,object等)
1,交叉类型(intersection types)
※,交叉类型是将多个类型合并为一个类型。 这让咱们能够把现有的多种类型叠加到一块儿成为一种类型,它包含了所需的全部类型的特性。 例如, Person & Serializable & Loggable
同时是 Person
和 Serializable
和Loggable
。 就是说这个类型的对象同时拥有了这三种类型的成员。
function extend<T, U>(first: T, second: U): T & U { let result = <T & U>{}; for (let id in first) { (<any>result)[id] = (<any>first)[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<any>result)[id] = (<any>second)[id]; } } return result; } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();
※,
※,
2,联合类型 (union types)
※,
/** * Takes a string and adds "padding" to the left. * If 'padding' is a string, then 'padding' is appended to the left side. * If 'padding' is a number, then that number of spaces is added to the left side. */ function padLeft(value: string, padding: string | number) { // ... } let indentedString = padLeft("Hello world", true); // errors during compilation
联合类型表示一个值能够是几种类型之一。 咱们用竖线( |
)分隔每一个类型,因此 number | string | boolean
表示一个值能够是 number
, string
,或 boolean
。
若是一个值是联合类型,咱们只能访问此联合类型的全部类型里共有的成员。
※,
3,
5,
十一,
1,
十二,
1,
《二》, 随记(问题及相关记录)
※,关于JavaScript的变量提高和函数提高
一,变量提高 console.log(a);//报错,a未定义 ------------------------------- console.log(a);//undefined var a=3 解析:实际执行顺序以下 var a;//变量提高,全局做用域范围内,此时只是声明,并无赋值 console.log(a);//undefined a = 3;//此时赋值 --------------------------------- 二,函数提高 函数有两种,声明式和字面量式(匿名函数),只有声明式会提高并且优先级高于变量提高 console.log(f1);//function f1(){} var f1 = "abc"; function f1(){} console.log(f1);//abc
f1();//报错,f1不是函数
解析:实际执行顺序以下 function f1(){}//函数提高,整个代码块提高到文件最开始(和变量提高不一样) console.log(f1);//function f1(){} var f1 = "abc"; console.log(f1);//abc
f1();//报错,此时f1="abc",不是函数,不能被调用。
※,JS函数的静态属性和实例属性
let A = function () { this.b= "bbbb"//b是函数A的实例属性 return ("xxx"); } A.c = "ccc";//c是函数A的静态属性 console.log(A.b);//undefined 实例属性不能用函数自己调用 console.log(A.c);//ccc 静态属性直接用函数自己调用 let a = new A(); l(a.b);//bbbb 实例属性能够在实例上调用
※