Fork me on GitHub

typescript2-基础知识(下)

类的一些基本示例:

1
2
3
4
5
6
7
8
9
10
11
12
class Greteer{
greeting: string
constructor(message:string){
this.greeting = message;
}
greet(){
return 'Hello,' + this.greeting
}
}
let greeter = new Greteer('world');
greeter.greet();

类里面允许使用继承来拓展现有的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal{
move(distance:number = 0){
console.log(`Animal move ${distance}`);
}
}
class Dog extends Animal{
bark(){
console.log('wolf!wolf!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);

一个更复杂的继承示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Animals {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance:number){
console.log(`${this.name} moved ${distance}m`);
}
}
class Snake extends Animals{
constructor(name:string){
super(name);
}
move(distance:number = 5){
console.log('Slitering ....');
super.move(distance);
}
}
class Hourse extends Animals{
constructor(name:string){
super(name);
}
move(distance:number=45){
console.log('Galloping...');
// 去给父类里面传递参数
super.move(distance);
}
}
let sam = new Snake('Sammy');
let tom:Animals = new Hourse('Tommy');
sam.move();
tom.move(12);

类的公私有,受保护:

1
2
3
4
5
6
7
8
9
10
11
class Animal0{
public name:string
public constructor(name:string) {
this.name = name
}
public move(distance:number = 0){
console.log(`${this.name} moved ${distance} m`);
}
}
new Animal0('cat')

如果name被设置为私有的话,那么new Animal('cat')就会报错.

1
2
3
4
5
6
7
8
class Animal1{
private name: string
constructor(name:string){
this.name = name;
}
}
// 这个地方就会报错
new Animal1('qaq');

可以这样来一波:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Animal0 {
private name: string;
constructor(name: string) {
this.name = name;
}
public move(distance: number = 0) {
console.log(`${this.name} moved ${distance} m`);
}
}
class Hippo extends Animal0 {
constructor() {
super("Hippo");
}
}
class Employee {
private name: string;
constructor(name:string){
this.name = name
}
}
let dongwu = new Animal0('Goat');
let hema = new Hippo();
let em = new Employee('Jason');
// dongwu 和 hema 共享了一个私有成员
dongwu = hema;
// 但是dongwu 和 em的name 实际上并不是同样的一个,都是各自的私有的
dongwu = em;

protect类和private类实际上是有一点相似的,只是父类中的private类是不能在子类中访问的,而protect类是能够在子类中访问的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Emp extends Person {
private dep: string;
constructor(name: string, dep: string) {
super(name);
this.dep = dep;
}
getPitch(){
return `Hello my name is ${name},and i work in ${this.dep}`
}
}
let zoomdong = new Emp('zoomdong','Alibaba');
console.log(zoomdong.getPitch());
// 这里调用受保护类的话还是会出现error
// console.log(zoomdong.name);

同样,如果在上面Person的构造函数前面加上protected,那么就不能使用Person去实例化对象了。

用过readonly来设置一些只读属性

1
2
3
4
5
6
7
8
9
class Person0{
readonly name:string
constructor(name:string) {
this.name = name;
}
}
let john = new Person0('john');
// 只读的类就不能修改了,下面就会报error了
john.name = 'xxx';

我们一般不去声明参数属性,直接在类里面把东西都写好就行了。

存取器

通过一个demo来看一下如何把一个简单的类改写为setget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 把一个普通的类改写为存取器的形式
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName() {
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) {
console.log(employee.fullName);
}

我们编译的时候将目标设置为ES5,采用下面这个命令:

1
2
tsc index.ts --target es5
node index.js

就会输出Bob Smith,如果我们修改passcode的值,那么就会输出Error:Unauthorized update of employee!0(这里就相当于不匹配了)。

这个看编译出来的结果会发现es5里面是使用Object.defineProperty来实现的一个原理。其实Vue的底层也相当于用这个实现的(一个响应式的原理)。

类的静态成员,静态属性只有在类里面才使用得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Grid {
static origin = { x: 0, y: 0 };
scale: number;
constructor(scale: number) {
this.scale = scale;
}
// 在里面封装一个计算距离的函数,实例传递进来的参数为放大倍数
claculateDistanceFromOrigin(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.claculateDistanceFromOrigin({ x: 3, y: 4 }));
// 输出为5
console.log(grid2.claculateDistanceFromOrigin({ x: 3, y: 4 }));
// 输出为25

抽象类

抽象类作为其他派生类的基类使用,他们是不能被实例化的。

抽象类的语法大概是下面这个样子的:

1
2
3
4
5
6
abstract class Animal{
abstract makeSound():void
move():void{
console.log('raoming the earth...');
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
abstract class Department {
name: string;
constructor(name: string) {
this.name = name;
}
printName(): void {
console.log(`Department name ${this.name}`);
}
abstract printMeeting(): void;
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting ad Auditing");
}
printMeeting(): void {
// 每天早上10点开会
console.log("The Accounting Department meets each Monday at 10am");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
// 我们已经声明了department为Department类型了
// 后面要调用那些方法,把这个类型改为AccountingDepartment类型即可
let department: Department;
department = new AccountingDepartment();
// 他可以调用抽象类里面的方法,但是无法调用派生类里面的方法
department.printName();
department.printMeeting();
// 这个地方就会报错
// department.generateReports();

类的高级技巧

这里设置一波,如果参数不传递的话,就设置返回standardGreeting,有参数回去的话,就返回对应的名字加上this.greeting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Greeter {
static standardGreeting = "Hello,there";
greeting: string;
constructor(message?: string) {
this.greeting = message;
}
greet() {
if (this.greeting) {
return `Hello, ${this.greeting}`;
} else {
return Greeter.standardGreeting;
}
}
}
let greeter: Greeter;
greeter = new Greeter();
console.log(greeter.greet());

如果我们想修改一波静态属性的话,可以在下面加上这样一些代码:

1
2
3
4
5
6
// 对静态变量去做一波修改
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = 'Hey There'
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

接口也可以当类来使用:

1
2
3
4
5
6
7
8
9
10
class Point{
x:number,
y:number
}
interface Point3d extends Point{
z:number
}
let point3:Point3d = {x:1,y:2,z:3}

但一般情况下不建议这样去使用。

函数

基本示例

可以先看一下简单的demo:

1
2
3
4
5
6
7
8
9
10
11
function add(x, y) {
return x + y;
}
let myAdd = function(x, y) {
return x + y;
};
let z = 1000;
function addToZ(x,y){
return x + y + z;
}

为函数添加一波类型(给函数添加一波参数类型)。

1
2
3
4
5
6
7
function add (x:number,y:number):number{
return x + y;
}
// 也可以设置一波变量类型,其实不设的话调用的时候也可以推断出来的
let myAdd:(baseValue:number,increValue:number) => number = function(x:number,y:number):number{
return x + y;
}

函数参数懒得讲了,可以自己去看一波文档,基本上和ES6里面的写法大同小异。

函数的this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// return function() {
// 这个地方改为箭头函数,因为他是函数创建的时候的this值
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickSuit = Math.floor(pickedCard / 13);
return {
// 这里this在ts里面会被推断为any
suits: this.suits[pickSuit],
card: pickedCard % 13
};
};
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log(`card: ${pickedCard.card} of ${pickedCard.suits}`);

this参数在回调函数里面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义一个接口里面有一个返回值为void的函数
interface UI {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
type: string;
onClickBad(this:void,e:Event){
// this.type = e.type;
console.log('clcked');
}
}
let h = new Handler();
let uiElement:UI = {
addClickListener(){
}
}
uiElement.addClickListener(h.onClickBad)

一般情况下我们要在onClickBad里面使用this的话是需要使用箭头函数来获得Handlerthis的.

1
2
3
onClickBad = (e:Event) => {
this.type = e.type;
}

所以看起来this导致的坑还是可使用箭头函数来搞定的.

ts里面的重载函数大概是可以写成这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let suits = ["hearts", "spades", "clubs", "diamonds"];
// 函数部分
function pickCard(x: { suit: string; card: number }[]): number;
// 重载部分将x由对象数组重载为数字
function pickCard(x: number): { suit: string; card: number };
function pickCard(x): any {
if (Array.isArray(x)) {
let pickCard = Math.floor(Math.random() * x.length);
return pickCard;
} 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 pickCard1 = myDeck[pickCard(myDeck)];
console.log(`card: ${pickCard1.card} of ${pickCard1.suit}`);
let pickCard2 = pickCard(15);
console.log(`card: ${pickCard2.card} of ${pickCard2.suit}`);

泛型

基本示例:

1
2
3
4
5
// 返回任何传入它的值,T同来捕获用户的传入类型
function identity<T>(arg:T):T{
return arg;
}
// 这里适用于多个类型,不会像any一样丢失类型

1
2
3
4
5
6
7
8
9
// 返回任何传入它的值,T同来捕获用户的传入类型
function identity<T>(arg:T):T{
return arg;
}
// 编译器识别不了的话则可以使用这种形式
let output0 = identity<string>('mystring')
// 这里编译器会自动帮我们推断出传入的值的类型
let output = identity('Mystring')

通常我们都推荐使用第二种方式。

1
2
3
4
5
function loginingIndetity<T>(arg:T[]):T[]{
// 如果arg:T这里就不会有length属性,我们将其修改为T[]
console.log(arg.length);
return arg;
}

声明泛型变量可以用两种方式:

1
2
3
let myIdentity: <T>(arg: T) => T = identity;
let myIdentity2: { <T>(arg: T): T } = identity;

我们可以使用泛型来写一个接口:

1
2
3
4
5
interface G{
<T>(arg:T):T
}
let myIdentity3:G = identity;

我们甚至是可以把T作为接口的参数:

1
2
3
4
5
interface G<T>{
(arg:T):T
}
let myIdentity3:G<number> = identity;

这样的好处在于我们不用在接口里面去描述一个泛型函数了。

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 把泛型的类型直接放在类的后面
class GenricNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenricNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y;
};
let stringNumberic = new GenricNumber<string>();
stringNumberic.zeroValue = "";
stringNumberic.add = function(x, y) {
return x + y;
};
console.log(stringNumberic.add(stringNumberic.zeroValue, "test"));

泛型类指的就是实例类型。

泛型约束

我们通过前面的一个代码实例来演示一下泛型约束要怎么进行:

1
2
3
4
5
6
7
8
9
// 给T一些约束,通过接口来进行约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}

也可以使用泛型去约束泛型:

1
2
3
4
5
6
7
8
9
10
// 这样申明之后,访问key的时候他都存在于T的属性里面。
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');
// 这个就会报错,因为z并不在x的key里面
getProperty(x,'z');

类型推断

let x = 3这个x会被自动推断成为数字类型。

1
2
// 这个时候a里面的元素会被推断成为联合类型
let a = [0,1,null];

上下文类型,ts会根据前面的一些类型去现有对象的一些类型。

1
2
3
4
window.onmousedown = function(mouseEvent){
// 这个地方会使用onmousedown去推断mouseEvent类型
console.log(mouseEvent.clientTime);
}

但是如果给mouseEvent加上一个any类型上面的代码编译就不会报错了。

高级类型

交叉类型

本质上就是将多种类型合并为一种类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
function extend<T, U>(first: T, second: U) {
let result = {} as T & U;
// 如果这样的话,T的类型是不能赋值为U的,我们就需要吧first的类型设置为any
for (let id in first) {
result[id] = first[id] as any;
}
for(let id in second){
if(!result.hasOwnProperty(id)){
result[id] = second[id] as any;
}
}
return result;
}

交叉类型大致上就是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person0{
constructor(public name:string){
}
}
interface loggable{
log():void
}
class ConSole implements loggable{
log(){
console.log(`qqaaa`);
}
}
// 我们把Person0和ConSole的类型通过extend拓展到一起,使得它成为一个联合类型
var item = extend(new Person0('wd'),new ConSole())
// item就相当于拿到了两种类型的属性和方法
item.name;
item.log();

联合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
// 声明一个长度加一的数组然后用空格来填充一波
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
// 类型不满足上面两种类型就报错
throw new Error(`Expected string or number got ${padding}`);
}
// 这个时候就能抛错误
padLeft("wdwddwdw", false);

联合类型取的是个交集,而交叉类型取的是一个并集。

-------------本文结束感谢您的阅读-------------