- TypeScript是微软开发的,基于类的面向对象编程,其文件以 .ts 为后缀名;
- TypeScript是JavaScript的超集,完全兼容JavaScript代码;
- TypeScript只存活于编译阶段,编译为JavaScript之后,在浏览器/Node环境下才能运行;
- TypeScript的安装与编译
npm i -g typescript tsc helloworld.ts
- 默认情况下,编译生成的js文件输出到当前目录下;
-
tsc helloworld.ts --outDir ./dist
:指定编译后的js文件输出到dist目录。
tsconfig.json
tsc --init //生成TS的配置文件tsconfig.json
{
"compileOnSave": true, //自动编译并保存
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"outFile": "./bundle.js",
"outDir": "./dist",
"strict": true,
......
"moduleResolution": "node", //按node编译
"removeComments": true, //生成JS代码后,移除注释内容
"sourceMap": true, //配合后期调试
"emitDecoratorMetadata": true, //支持元数据,装饰器需要使用
"typeRoots": [] //为TS编译器指定检查类型的依据文件
},
"include": [],
"exclude": []
}
-
tsconfig.json
:用于配置tsc
的编译配置选项,如js文件的输出目录; - 当
tsc
不指定要编译的ts文件时,编译器会从当前目录开始逐级向上查找tsconfig.json
- 当指定了编译的ts文件时,
tsconfig.json
会被忽略; -
--project(-p)
:指定一个包含tsconfig.json
的目录来进行编译。
编译选项:compilerOptions
-
outDir
:编译生成的js文件的输出目录,当前目录(./)
就是tsconfig.json
的所在目录; -
outFile
:合并输出到一个JS文件中,合并文件的顺序为加载和依赖顺序; -
module
:编译后的js所使用的模块化系统,none、commonjs、es2015、amd、esnext ...
-
target
:指定编译后的js对应的ECMAScript版本,es3、es5、es6、es2015、es2016、es2017 ...
指定要编译文件
- 执行
tsc
命令,但不指定任何ts文件时,默认会编译当前项目中的所有ts文件; -
include
:指定要编译的ts文件目录"include": [ "./src/" ] //编译src目录下的ts文件
- 使用
glob
模式,类似于正则表达式,**/
递归匹配任意子目录; -
*
匹配0或多个字符,?
匹配一个任意字符,但都不包括目录分隔符; -
./src/*
:只编译src目录下的ts文件,不包括src的子目录; -
./src/**/*
:递归编译src目录下的ts文件,包括子目录;
- 使用
-
exclude
:指定不编译的ts文件目录,默认已经排除了node_modules
和outDir
目录
数据类型
- let 变量: 类型
let input: HTMLInputElement = document.querySelector('uname'); let value: number = Number(input.value) + 10; let a: string; a = 1; //报错
- TS的类型:
数字、字符串、布尔型、null、undefined、数组、元组、枚举、void、any、Never
- 基本类型与包装类型
- 基本类型:
string、number、boolean
- 包装类型:
String、Number、Boolean
- 基本类型可以直接赋值给对应的包装类型,但反之不行。
let s: String = 'nodejs';
- 基本类型:
- 布尔类型
var flag:boolean = true; flag = false;
- 数字类型
var n:number = 12;
- 字符串类型
var s:string = 'hello ts';
- 数组类型
- 基本语法:
var arr:number[] = [1, 2, 3]; //元素类型为number的数组
- 泛型方式:
var arr:Array<number> = [1, 2, 3];
- 基本语法:
- 元组类型:数组的一种,可以存储多种类型的元素,但 顺序不可以颠倒,长度不可以违规,是固定的;
let tup:[number, string] = [11, 'hello']; tup[0] = 20; tup[1] = '元组';
v2.6
之前,超出规定个数的元素称作越界元素,但是只要越界元素的类型是声明类型中的一种即可,属于联合类型,所以没问题。但v2.6
之后,要求元组赋值的类型和个数都必须保持一致。 - 枚举类型
enum 枚举名{ 标识符[=整型常数], 标识符[=整型常数], 标识符[=整型常数], }
- 标识符可以加引号,也可以不加,
[]
表示可选参数
enum Flag { success=1, error, 'undefined'=-1 }
- 使用枚举
let f:Flag = Flag.success; console.log(f); // 1
- 如果标识符都没有赋予整型常数,则默认为从
0
开始的角标。
- 标识符可以加引号,也可以不加,
- 任意类型
var num:any = 123; num = false;
-
null
和undefined
,定义了变量,但并未赋值,默认是undefined
var num:undefined; console.log(num); // undefined, var num:null = null; console.log(num); // null
- 复合类型
var num:number | undefined | null; // num 可以是数字类型,也可以是undefined/null console.log(num); // undefined num = 100; console.log(num); // 100
-
void
类型:表示没有任何类型,一般用于定义没有返回值的函数;function run():void { console.log(123) }
- never类型:表示从不会出现的值,即声明为never类型的变量只能被never类型所赋值;
var num:never; num = (()=>{ throw new Error('some happend'); //抛出异常,不属于任何已知类型 })();
- 类型推导
- 有时候不一定强制使用类型声明,TS会根据语境进行类型推导;
- TS的变量初始化推导
let a; a=1; //变量a 是number类型,不允许再赋予其他类型的值;
- TS的上下文推导
btn.onclick = function(e) {} //e: MouseEvent btn.onkeydown = function(e) {} //e: KeyboardEvent
- TS会根据当前绑定的事件,推导出回调函数的第一个参数类型
MouseEvent/KeyboardEvent
函数
- 函数声明
function fn(x: Type, y: Type): Type { ... } function fn(x: number, y: number): number { //参数为number,返回值为number return x+y; } //匿名函数 let fn = function(x: number, y: number): number { return x+y; }
- 函数表达式
let fn:(x:Type, y:Type) => Type = function(x:Type, y:Type){ ... } let fn: (x: number, y: number) => number = function(x: number, y: number) { return x+y; } // 函数体function的参数类型可以省略,ts会进行类型推导 let fn: (x: number, y: number) => number = function(x, y) { return x+y; }
- 形参的对象约束
- 如果传入的是匿名对象,其属性必须与约定属性一致;
function print(label:{name:string}):void { console.log('print ', label.name); } print({name:'Machel'}); //print Machel
- 如果不是匿名对象,则只需要包含约定的属性即可;
let obj = { name: 'Machel', age: 20 } print(obj); //print Machel
- 可选参数:与ES6保持一致,必须配置在形参的末尾,且默认值为
undefined
function run(name:string, age?:number):string { return 'hello ts'; } run('Jack'); run('Jack', 20);
- 默认参数:指定形参的默认值,也必须配置在形参的末尾
function run(name:string, age:number=20):string { return 'hello ts'; }
- 如果手动指定了参数的默认值,则不能再声明为可选参数
?
- 对于默认参数,可以利用TS的自动类型推导,从而不声明类型。
- 如果手动指定了参数的默认值,则不能再声明为可选参数
- 剩余参数:三点运算符
function sum(name:string, ...rest:number[]):number { //name是一个必传参数(字符串类型),其余所有参数(number类型)都被封装在 rest 数组中 let sum:number = 0; for(let i=0; i<rest.length; i++) { sum += rest[i]; } return sum; } sum('Machel', 1, 2, 3); sum('Machel', 1, 2, 3, 4, 5);
- 函数重载:TS需要兼容ES5和ES6,所以TS的函数重载与Java的函数重载略有不同;
- 参数个数相同
function run(name:string):string; function run(age:number):string; function run(sn:any):any { if(typeof sn === 'string') { // string return 'name is ' + sn; } else { // number return 'age is ' + sn; } } run('Jackson'); // name is Jackson run(12); // age is 12 run(true); // 编译报错
- 参数个数不同:借助可选参数
function run(name:string):string; function run(name:string, age:number):string; function run(name:any, age?:any):any { if(age) { // age 存在 return `name: ${name}, age: ${age}`; } else { // age 不存在 return `name is ${name}`; } }
- 函数的
this
- ts的函数中,
this
默认指向any
,ts不能对any
类型提示任何属性和方法; - 在
tsconfig.json
中,取消this
默认指向any
的设置:
"compilerOptions": { "noImplicitThis": true }
- 对于某些情况,如DOM事件,回调函数的
this
默认指向DOM对象,TS自动推导。
- ts的函数中,
类
ES5定义类
- 构造函数
function Persion() { this.name = 'Machel'; this.age = 20; this.show = function() { console.log(this.name, this.age); } } var p = new Persion(); p.show(); // Machel 20
- 在原型链上扩展属性和方法
Persion.prototype.sex = 'male'; Persion.prototype.work = function() { console.log(this.name + 'is work!'); } p.work();
- 原型链上的属性和方法会被多个实例共享,而构造函数中的属性和方法只是拷贝一份给每个实例;
- 静态方法
Persion.run = function() { console.log('run'); } Persion.run();
- 继承:原型链、对象冒充,以及两种模式的组合
- 对象冒充
function Web() { Persion.call(this); // Web继承Persion } var w = new Web(); w.show(); // Machel 20 w.work(); // 报错:对象冒充不能继承原型链上的属性和方法
- 原型链继承
function Web() { } Web.prototype = new Persion(); w.show(); // Machel 20 //但是这种方式无法给父类传参 function Persion(name, age) { this.name = name; this.age = age; this.show = function() { console.log(this.name, this.age); } } Web.prototype = new Persion(); var w = new Web('Machel', 22); w.show(); //undefined undefined,接收不到参数!
- 组合模式
function Web(name, age) { Persion.call(this, name, age); } Web.prototype = new Persion(); 或 Web.prototype = Persion.prototype; var w = new Web('Machel', 22); w.show(); // Machel 20 w.work(); // Machel is work!
- 需要百度,详细看看ES5中的类
TS的类
- TS的类与ES2015(es6)中的
class
类似,同时新增了一些实用特性; - 类的定义
class Person { name:string; //属性,默认访问修饰符为public,默认值为undefined constructor(name:string) { this.name = name; } getName():string { return this.name; } run():void { console.log(this.name + ' 在 Person'); } } var p = new Person('Jackon'); console.log(p.getName()); // Jackon p.run(); // Jackon 在 Person
- 类的继承:
extends
,单继承class Web extends Person { constructor(name:string) { super(name); //必须先初始化父类的构造器 } run():void { //覆写父类的方法 console.log(this.name + ' 在 Web'); } } var w = new Web('Machel'); console.log(w.getName()); // Machel w.run(); // Machel 在 Web
- 修饰符:
public、protected、private、readonly
-
public
:公有,在类内部、子类、类外部(对象)都可以访问,默认修饰符 -
protected
:保护类型,在类内部、子类可以访问,类外部(对象)不能访问; -
private
:公有,在类内部可以访问,子类、类外部(对象)不能访问; -
readonly
:只读,对象只能获取,不能重新赋值; - 在构造函数的参数上使用修饰符,表示同时在类中创建该属性,该属性不能在类中预定义;
constructor(public age: number){ //为Person创建属性age:public age: number; this.age = age; }
-
- 存取器:
setter/getter
的简写形式private _age: number = 10; get age(): number { //访问:p1.age; return this._age; } set age(age: number) { //访问:p1.age = 20; this._age = age; }
- 静态属性、方法:
static
修饰,静态方法中没有this
,所以只能访问静态属性;class Person { public name:string|undefined; static age:number = 20; static print() { console.log('print ' + Person.age); } } Person.age; // 20 Person.print(); //print 20
- 多态:继承的一种表现,父类引用指向子类对象!
var p:Person = new Web('Machel'); p.run(); // Machel 在 Web
抽象类
abstract
:修饰抽象类和抽象方法
- 抽象类不能直接实例化,抽象方法不包含具体实现;
- 抽象类中可以同时包含抽象方法和普通方法,但抽象方法只能放在抽象类中;
- 抽象类的子类如果不是抽象类,则必须实现父类的抽象方法!
abstract class Animal { public name:string; constructor(name:string) { this.name = name; } abstract eat():any; } class Dog extends Animal { constructor(name:string) { super(name); } eat() { console.log(this.name + ' is eat!') } } var d:Animal = new Dog('dog'); d.eat(); // dog is eat!
接口:interface
- 属性接口,约束函数的对象形参
interface Options { width: number, height: number } function print(opts: Options){ console.log(opts.width, opts.number); } print({width:100, height:50})
- TS类型检测器只会检查接口所定义的规则属性是否存在,并不会检查属性的顺序;
print({height:50, width:100})
- 当以匿名对象的方式传入时,必须严格遵守接口规则,不能有多余属性;
print({ firstName: 'Jack', secondName: 'Machal', age: 1 //编译报错:匿名对象的形式传入时,只能包含接口中约束的属性 });
- 如果传入的对象不是一个匿名对象,那么只需要包含接口规则的属性即可;
var obj = { firstName: 'Jack', secondName: 'Machal', age: 1 } print(obj); //编译通过:Jack Machal
-
as
断言可以绕开TS检测
print({ width: 100 } as Options); //编译通过
- 可选属性的接口
interface FullName { firstName:string; secondName?:string; //可选属性 } print({ firstName: 'Jack' //不传secondName属性 })
- 函数接口
interface Func { (key:string, value:string):string; } var fn:Func = function(key:string, value:string):string { return key + value; } fn('Jackson', '123456'); //Jackson123456
- 可索引接口:数组、对象的约束,也就是一组
key-value
的数据,数量是不确定的,其中的key具有某种特性;- key的特性在于:只能是
string
或number
- 数组约束
interface UserArr { [index:number]:string //索引为number类型,值为string类型 } var arr:UserArr = ['aaa', 'bbb'];
- 对象约束
interface UserObj { [index:string]:string //属性名和属性值都是string类型 } var obj:UserObj = {name:'Joker', age:'20'}
- TS类是不允许对象自己直接扩展属性和方法的,但可以通过接口去扩展!
class Person { name = 'Mack' } let p = new Person(); p.run = function(){ } //编译报错! interface Person { [attr: string]: any } p.fly = function() { //编译通过 console.log('fly: ', this.name); } p.fly(); // fly: Mack
- key的特性在于:只能是
- 类类型接口:对类的约束,有点类似于抽象类
interface Animal { name:string; eat(foot:string):void; } class Dog implements Animal { name:string; constructor(name:string) { this.name = name; } eat() { //可以只实现接口要求的方法,忽略要求的参数 console.log('eat...'); } }
- 接口的继承
interface Animal { eat():void; } interface Person extends Animal { work:void; } class Web implements Person { public name:string; constructor(name:string) { this.name = name; } eat() { console.log('eat...'); } work() { console.log('work...'); } }
- 一个类可以同时实现多个接口,但只能继承一个类
class A extends B implements C,D { }
泛型
- 泛型变量
function getData<T>(value:T):T { return value; } getData<number>(123); //number类型 getData<string>('Machel'); //string类型 function getData3<T>(value:T):void { console.log(value) } getData<number>(123); // 123
- 泛型也可以有多个
function fn<T, S>(a:T, b:S): [T, S] { }
- 还可以是数组形式
function fn<T>(a: T[]): T[] { } function fn<T>(a: Array<T>): Array<T> { }
- 泛型类
class Min<T> { public list:T[] = []; add(value:T):void { this.list.push(value); } min():T { var m = this.list[0]; ... return m; } } var n = new Min<number>(); n.add(2);
- 泛型作为一种类型
let fn: <T>(x: T, y: T) => number = function(x, y) { return Number(x) + Number(y); }
- 泛型接口
- 方式一
interface IFn { <T>(x: T): T; } var fn:IFn = function<T>(x:T):T { return x; } fn<string>('Joker'); //Joker
interface IFn<T> { (x: T, y: T): number } let fn: IFn<string> = function(x, y) { return Number(x) + Number(y); }
- 方式二
interface IFn<T> { (x:T):T; } function fn<T>(x:T):T { return x; } var myGet:IFn<string> = fn; myGet('Joker');
- 泛型约束:
extends
,约束泛型的类型范围- 约束泛型为HTML节点对象
function fn<T extends HTMLElement> (ele: T) { }
- 配合接口使用
interface Len { length: number } function fn<T extends Len> (e: T) { } fn(1); //编译报错:number类型没有实现 Len 接口,也不具备 length 属性 fn('2'); //string类型实现了 Len 接口
- 类类型
- 如何让一个外部函数成为创建对象的工厂
function getArray(constructor: Array) { return new constructor(); } let arr = getArray(Array); //编译报错
- 形参constructor表示Array类型的对象,而不是一个Array的构造函数,所以无法创建对象;
-
{new()}
:表示构造函数类型
function getInstance(constructor: {new()}) { return new constructor(); } let arr = getInstance(Array); //通过TS检查,创建一个数组对象
- 限制构造函数的类型:
function getInstance(ct: {new(): Array<string>}) { return new ct(); //只能创建Array<string>类型的对象 } function getInstance<T>(ct: {new(): T}) { //泛型 return new ct(); }
- 应用:数据库操作的封装
class MySqlDB<T> { // add(info:T):boolean { //...... 把info中的属性插入到对应的表中 return true; } } class User { //数据库表的映射对象 username:string; password:string; constructor(params:{ username:string, password:string }) { this.username = params.username; this.password = params.password; } } var u = new User({username:'Joker', password:'123456'}); var DB = new MySqlDB<User>(); DB.add(u);
模块
- 内部模块称为命名空间,外部模块称为模块;
- 模块在自身作用域中执行,而不影响全局作用域,即模块中的变量、函数、类等等对外部是不可见的;
- 外部要使用模块内的数据,必须先让模块通过
export
暴露出里面的数据,外部再通过import
引入这些数据即可;
(外部)模块
- 创建目录
modules
,用于存放项目的模块,创建一个模块db.ts
var url = 'xxxxxx'; export function getData():any[] { return [ { title: '111' }, { title: '222' }, ] } export function save() { console.log('save...') }
- 在其他模块中引入
db.ts
模块中的方法import { getData } from './modules/db';
- 引入时起别名
import { getData as get } from './modules/db';
- 但是,
export、import
会被编译为exports、require()
,浏览器默认不支持,需要使用node
命令执行,或者借助webpack
这样的工具编译为浏览器能执行的代码; - 暴露方式二
export { url, getData, save }
- 暴露方式三:
export default
-
export
可以导出多次,而export default
只能导出一次
export default getData;
-
export default
导出的是什么数据,import
导入的就是什么数据
import getData from './modules/db';
-
命名空间
- 在一个模块中,为了避免各种变量命名相冲突,可以将相似功能的函数、类、接口等放到独立的命名空间内;
namespace A { interface Animal { name:string; } } namespace B { interface Animal { name:string; } }
- 命名空间内的数据默认是私有的,外部要使用这些数据,也必须通过
export
暴露出去;
namespace A { interface Animal { name:string; } export class Dog implements Animal { name:string='Joker'; eat() { console.log(this.name + ' eat'); } } } var d = new A.Dog(); d.eat(); // Joker eat
- 命名空间内的数据默认是私有的,外部要使用这些数据,也必须通过