假如翻到了此文章,如果你写过类似java,c#等强类型面向对象语言,就可以阅读此文,迅速入手TS,否则就移步到此:https://ts.xcatliu.com/introduction/what-is-typescript.html
ts:
优点:1,增加了代码的可读性和可维护性(编译阶段就发现大部分错误,优越于运行时发现错误)
2,TypeScript 最大的优势便是增强了编辑器和 IDE 的功能,包括代码补全、接口
提示、跳转到定义、重构等。
1、 原始类型
boolen number null undefine stirng object
2、任意值:
let anything:any="sss"
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
3、未声明类型—>识别成any
let somethine;
//此处没有声明类型
4、类型推论
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
5、联合类型
let myFavoriteNumber: string | number|bollen|null
此时:myFavoriteNumber='harry' myFavoriteNumber='number' myFavoriteNumber=true
等都是对的
参数:
function getLength(something: string | number): number {
return something.length; //注意:此时如果不是联合类型的共有属性就会报错
}
6、接口:
命名规范 I开头,如Iperson{} //
1、在没有可选属性,任意属性前提下实现接口时属性数量必须跟接口一致
2、任意属性/可选属性:
interface:Iperson{
name: string;
age?: number; //可选属性:这样写,实现接口时,这属性有无均可
[propName: string]: any; //任意属性,这个字段随便写
}
3、只读属性
interface Person {
readonly id: number; //只读
name: string;
age?: number;
[propName: string]: any;
}
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
error:
此处实现接口的时候没有对只读属性id进行赋值
let tom: Person = {
name: 'Tom',
gender: 'male'
};
error:
因为是只读不能修改,所以error:tom.id = 89757;
数组:
1、常规数组:let stringArr:string[]=["A","B","C","D"]
let numberArr:string[]=[1,2,3,4]
2、数组泛型
let Tarr:Array<>
例子:
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
let fibonacci: Array<any> = [1, 1, 2, 3, 5];
//任意类型
3、用接口表示数组
interface NumberArray{
[index:number]:number
}
4,any 在数组中的应用
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
//数组中各种类型数据都可以有
5,类数组-不是数组类型
function sum() {
let args: number[] = arguments;
}
函数:
function sum(x: number, y: number): number {
//指定参数类型,指定返回值类型
return x + y;
}
sum(1, 2);
//传参多,少,类型不对都会报错
1、函数定义
写法1:
let mySum = function (x: number, y: number): number {
return x + y;
};
这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。
推荐写法:
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。
在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
采用接口定义函数
interface IDemoFun {
(source: string, sub: string): boolean;
}
let demoFun: IDemoFun;
demoFun = function(source: string, sub: string) {
return source.search(sub) != -1;
};
可选参数:可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了
function buildName(firstName: string, lastName?: string) { //?可选可以不传
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
参数默认值:我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数:
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
剩余参数:
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
items 是一个数组。所以我们可以用数组的类型来定义它:
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
函数重载:允许一个函数接受不同数量或类型的参数时,作出不同的处理
function reverse(x: number): number; //重载1
function reverse(x: string): string; //重载2
function reverse(x: number | string): number | string { //实现函数
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
类型断言(Type Assertion):可以用来手动指定一个值的类型。
语法:<类型>值
function getLength(something: string | number): number {
if (something.length) { //error:此处报错因为number类型是没有length属性的
return something.length;
} else {
return something.toString().length;
}
}
使用断言:
function getLength(something: string | number): number {
if ((<string>something).length) { //<类型> 值
return (<string>something).length;
} else {
return something.toString().length;
}
}
文件声明:
1、声明语句
以jquery为例
通常页面使用jquery $("#id") 但是ts并不知道这是什么意思
所以我们要声明引入的第三方库
declare var jQuery: (selector: string) => any;
2、声明引入文件
@types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery --save-dev
内置对象:
用 TypeScript 写 Node.js
Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:
npm install @types/node --save-dev
类型别名
使用 type 创建类型别名
例如:
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
//使用别名
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
字符串字面量类型:用来约束取值只能是某几个字符串中的一个。
type EventNames = 'click' | 'scroll' | 'mousemove'; //使用type定义了字面量类型
function handleEvent(ele: Element, event: EventNames) { //使用了字面量类型
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 没问题 //在约束范围
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick' 不在约束范围
元组:数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
let arr:[string,number,boolen]=["fuck",123,true]
当赋值或访问一个已知索引的元素时,会得到正确的类型:
let xcatliu: [string, number];
xcatliu[0] = 'Xcat Liu';
xcatliu[1] = 25;
xcatliu[0].slice(1);
xcatliu[1].toFixed(2);
let xcatliu: [string, number];
xcatliu[0] = 'Xcat Liu';
越界元素:当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型:
let xcatliu: [string, number];
xcatliu = ['Xcat Liu', 25];
xcatliu.push('http://xcatliu.com/');
xcatliu.push(true);
枚举(Enum):类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等,一般枚举有两种:常数项和计算所得项
1、常数项-普通枚举
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
手动赋值:未手动赋值的枚举项会接着上一个枚举项递增。
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
手动自动区别:
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的:
当然也可以是小数或者负数,但是后续增长得增步长依旧是1
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true
2、计算所得项
enum Color {Red, Green, Blue = "blue".length};
//标红即是计算所得项
但是如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错:
枚举成员被当做常数条件
不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 1。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 0。
枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常数枚举表达式:
数字字面量
引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用
带括号的常数枚举表达式
+, -, ~ 一元运算符应用于常数枚举表达式
+, -, *, /, %, <<, >>, >>>, &, |, ^ 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错
所有其它情况的枚举成员被当作是需要计算得出的值。
常数枚举和普通枚举区别:常数枚举会在编译阶段被删除,并且不能包含计算成员。
3、常数枚举
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
4、外部枚举:declare enum 定义的枚举类型:
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
declare:
定义的类型只会用于编译时的检查,编译结果中会被删除
类:
类(Class):
定义了一件事物的抽象特点,包含它的属性和方法
对象(Object):
类的实例,通过 new 生成
面向对象(OOP)的三大特性:
封装、继承、多态
封装(Encapsulation):
将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
继承(Inheritance):
子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
多态(Polymorphism):
由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
存取器(getter & setter):
用以改变属性的读取和赋值行为
修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
抽象类(Abstract Class):
抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
接口(Interfaces):
不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
es6/7中的类的体现
类:
class Animal {
name:string;
constructor(name) {
this.name = name;
}
sayHi() {
return `my name is ${this.name} `;
}
}
let a = new Animal("harry");
console.log(a.sayHi());
继承:
class Cat extends Animal {
constructor(name) {
super(name);
}
sayHi() {
return "Cat" + super.sayHi();
}
}
let c = new Cat("Cat");
console.log(c.sayHi());
存取器:
class An{
name:string;
constructor(name){
this.name=name
}
get(){
return this.name
}
set(value){
console.log("set",value)
}
}
let A=new An("what the fucker")
console.log("get",A.get()) //获取
console.log(A.set("lalla"))//写入
静态属性:static直接通过类来调用
class Anim {
static isAnimal(obj) {
return obj instanceof Anim;
}
}
let ani = new Anim();
console.log("static", Anim.isAnimal(ani)); //直接通过类调用
TS中类的用法
修饰符:
public:
公共权限,默认属性,任何地方都能访问到
private:
私有的,只能在类内部使用
class ClassDemo {
private name: string;
public constructor(name) {
this.name = name;
}
}
let cd = new ClassDemo("harry");
console.log(cd.name); //error
无法访问,只能在类内部使用
protect:
私有,在类和子类中使用
class ClassDemo {
protected name: string;
constructor(name) {
this.name = name;
}
}
class pro extends ClassDemo{
constructor(name){
super(name)
}
}
let p=new pro("sssssss")
p.name="ddd" //error 只能在子类内使用
抽象类:用于定义抽象类和其中的抽象方法
继承抽象类必须实现其中的方法
abstract class absDemo {
protected name: string;
constructor(name) {
this.name = name;
}
protected abstract sayHi();
}
class demo extends absDemo { //继承抽象类
constructor(name) {
super(name);
}
sayHi() { //必须实现抽象类中的方法
console.log("xxxx", this.name);
}
}
let d=new demo("harry")
d.sayHi();
类和接口的继承关系
1、类实现接口
interface tool {
open();
}
class Door {
type: string;
DoorType(type) {
this.type = type;
}
}
class car extends Door implements tool { //继承类,实现接口
open() {
console.log("open car doors");
}
DoorType() {
console.log("type", this.type);
}
}
let C = new car();
C.open();
C.DoorType();
C.type = "CAR";
2、类实现多个接口
interface tool {
open();
}
interface Door {
DoorType(type);
}
class car implements tool, Door { //实现多个接口
open() {
console.log("open car doors");
}
DoorType(type) {
console.log("type", type);
}
}
let C = new car();
C.open();
C.DoorType("car")
3、接口继承接口
interface tool {
open();
}
interface Door extends tool { //接口继承接口
DoorType(type);
}
class car implements tool, Door {
open() {
console.log("open car doors");
}
DoorType(type) {
console.log("type", type);
}
}
let C = new car();
C.open();
C.DoorType("car")
4、接口继承类
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
5、混合继承
interface SearchFunc {
(source: string, subString: string): boolean; //接口的方式来定义一个函数需要符合的形状:
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
//接口中有时根据子类需求还有公共字段属性
interface Counter {
(start: number): string;
interval: number; //字段成员
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0; //使用字段
泛型:在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function createArray<T>(length: number, value: T): Array<T> { //泛型典型应用
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
元组:多参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T { //继承接口,自动约束必须有length属性
console.log(arg.length);
return arg;
}
loggingIdentity(7); //error:因为参数不包含length属性就会报错
参数相互约束
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
上例中,我们使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。
泛型接口:
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
泛型类:
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
//指定默认类型
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
声明合并:
函数合并
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
interface Alarm {
price: number;
}
interface Alarm {
price: string; // 类型不一致,会报错
weight: number;
}
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
接口合并:
}interface Alarm {
price: number;
}
interface Alarm {
weight: number;
}
相当于:
interface Alarm {
price: number;
weight: number;
}
注意,合并的属性的类型必须是唯一的:
interface Alarm {
price: number;
}
interface Alarm {
price: number; // 虽然重复了,但是类型都是 `number`,所以不会报错
weight: number;
}
============
interface Alarm {
price: number;
alert(s: string): string;
}
interface Alarm {
weight: number;
alert(s: string, n: number): string;
}
相当于:
interface Alarm {
price: number;
weight: number;
alert(s: string): string;
alert(s: string, n: number): string;
}
代码检查:
tslint eslint