typescript学习二

window.pageYOffset || document.documentElement.scrollTop获取滑动距离。

1.问题一:typescript既有type annotations也有type inference,那么问题来了,如果定义一个变量,但是既不显示说明类型,又不进行赋值的话,那么他在typescript里面是什么类型呢?

答案:是any类型。但是呢,最佳实践是尽量少用any类型。

2.void类型,应用场景举例:加入一个函数只是打印日志的话,那么显然它是不具备什么返回值的,那么我们可以显示声明他的返回值是void,也可以借助typescript的type inference来推断出返回类型是void类型。如下所示:

1
2
3
4
// 可以不type annotations,借助type inference
function log(text: string): void {
console.log('primitive type: void', text);
}

3.never,这里粗略带过,与void所不同的是,void表示函数会把执行权力给移交出去,但是不具有返回值;而never表示的是函数永远执行,不返回。同时,typescript对于never的实现还不完善。比如下面这个例子如果把type annotations给去掉,借助typescript的type inference的话,那么会被推断为void。

1
2
3
4
5
function loop(taskName: string): never {
while(true) {
console.log('run', taskName);
}
}

4.enum类型,话不多说,直接看例子:

1
2
3
4
5
6
7
8
enum Status {
Paid,
Sending,
Cancel,
Completed
};

let status: Status = Status.Paid; // status == 0

枚举默认从0开始,但是也可进行修改:

1
2
3
4
5
6
7
enum Status {
Paid = 1,
Sending,
Cancel,
Completed
};
let status = Status.Sending; // status == 2

也可以我都要插一手:

1
2
3
4
5
6
7
enum Status {
Paid = 1,
Sending = 2,
Cancel,
Completed = 5
};
let s1: Status = Status.Cancel; // s1 == 3

5.interface是啥子:An interface is a contract that defines a type with a collection of property and method definitions without any implementation。注意对于方法而言,interface无需实现。

使用interface所定义的类型去声明变量的时候,如果约定的东西和实际赋值的东西不同的话那么则会报错。

interface之optional properties:

1
2
3
4
5
6
interface OrderDetail {
price: number;
amount: number;
buyDate?: Date; // 这是一个可选参数
getTotal(discount: number): number;
};

function之optional param:

1
2
3
4
function fun(s1?: string) {
let s = s1 || 'afk';
console.log(s);
}

interface之readonly properties:

1
2
3
4
5
6
7
8
9
interface Product {
readonly name: string;
price?: number;
};
let p1: Product = {
name: 'loyalty',
price: 0.1
};
p1.name = 'someOther'; // error

interface之extending interfaces:Interfaces can extend other interfaces so that they inherit all the properties and methods from its parent. 看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
interface Product {
price: number;
amount: number;
}
interface A extends Product {
discount: number;
}
let a: A = {
price: 10,
amount: 3,
discount: 0.1
};

6.type关键字定义一个类型,啥我都当,如下所示:

1
type getTotal = (discount: number) => number;   // type关键字指代函数类型

type aliases can also define the shape of an object.

1
2
3
4
type Product = {
name: string,
unitPrice: number
}

7.typescript中的class,有点像interface和type,如下所示:

1
2
3
4
5
6
7
class Product {
name: string;
price: number;
}
let p = new Product();
p.name = 'aha';
console.log(p.name, p.price); // aha undefined

从上面也有一个疑问,引入了类型,但是price类型是number,输出却是undefined?其实,在typescript里面像下面这样定义也是行的:

1
2
3
4
5
6
7
let a: number = undefined;
let a: null = undefined;
type O = {
name: string;
};
let o: O = null;
console.log(o.name);

这一点,很让人失望。

class之implementing interfaces,有什么好处暂时也不知道:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IOrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number;
}

class OrderDetail implements IOrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number {
return this.quantity;
}
}

class之extend class,举例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
class Parent {
job: string;
}
interface IChild {
name: string;
age: number;
}
class Child extends Parent {
meta: IChild;
}
let c = new Child();
console.log(c.job, c.meta); // undefined undefined

如果父类中含有构造函数的话,那么子类也会将constructor给继承过来:

1
2
3
4
5
6
7
8
9
10
11
12
class Parent {
constructor(public job: string) {}
}
interface IChild {
name: string;
age: number;
}
class Child extends Parent {
c: IChild;
}
let c: Child = new Child('oooo');
console.log(c.job);

由于将父类的constructor也给继承过来了,所以在new的时候如果不传入参数的话将会报错。

如果子类也实现了constructor的话,那么必须调用super方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Parent {
constructor(public job: string) {}
}
interface IChild {
name: string;
age: number;
}
class Child extends Parent {
constructor(public c: IChild, public job: string) {
super(job);
}
}
let p: IChild = {
name: '3h',
age: 23
};
let c = new Child(p, 'orange');
console.log(c.job, c.c); // 输出 orange { name: '3h', age: 23 }

class之abstract classes:

关于定义:Abstract classes are a special type of class that can only be inherited from and not instantiated,可以看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class AClass {
name: string;
age: number;
abstract fun(): void;
}

class Child extends AClass {
constructor(public name: string, public age: number) {
super();
}
fun(): void {

}
}
let c = new Child('3h', 23);

class之Access modifiers:

对于一个类中所定义的方法和属性来说,访问权限都是默认为public的。这意味着这些属性以及方法在实例中以及子类下都是可访问的。同时,我们也可以显式的使用Access modifiers,只需要在属性名或者方法名之前加上Access modifiers谓词即可。

Access modifiers之private:这意味属性成员或者方法成员只能在class里面访问到,在class instance和child class中是无法访问到的。

1
2
3
4
5
6
7
8
9
10
11
12
class OrderDetail {
public price: number;
public name: string;
private deleted: boolean;
public delete(): void {
this.deleted = true;
}
}

let o = new OrderDetail();
o.delete();
// o.deleted; // 像这样访问会报错

class之Property setters and getters:

我们可以对property应用一个getter方法和setter方法,对于class的private属性来说这将会变得特别有用,下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass {
constructor(public name:string='3h', private _age:number=0) {}
get age(): number {
return this._age;
}
set age(value: number) {
this._age = value;
}
}
let c = new MyClass();
console.log(c.name, c.age); // 3h 0
c.age++;
console.log(c.age); // 1

class之static:

我们可以对property和methods使用static,这表明这些被声明为static的property以及methods都是属于class自身的,因此在static方法内部是访问不到类实例的,下面介绍一个例子:

1
2
3
4
5
6
7
8
9
10
class MyClass {
constructor(public name: string='3h') {}
static getName(): void {
console.log(this.myName); // 提示报错,在MyClass对象上找不到该属性
}
static myName: string='haha'; // 加上这行的话,则上面就不会报错了
}
let c = new MyClass();
console.log(c.name);
MyClass.getName();

8.typescript工程中的模块机制:默认情况下,你在A文件顶部作用域中所定义的变量都会被挂载到全局作用域中,除非你使用export关键字导出。因此这就造成了一个情况:如果你在A文件中定义了一个interface A的话,并且没有export的话,那么你在B文件中直接使用interface A也不会报错,因为他被挂载到了全局作用域之下。而这就造成了一个问题,那就是容易造成命名冲突问题。

9.turple之open-ended turples

1
2
3
type Scores = [string, ...number[]];
let s1: Scores = ['blue', 1, 2, 3];
let s2: Scores = ['mj', 4, 5, 6, 7];

10.turple function parameters之作为函数的参数处理,避免js自身利用rest参数作为函数参数的不足。

1
2
3
4
5
6
type Scores = [string, ...number[]];

function fun(...param: Scores) {
console.log(param);
}
fun()

11.对于spread运算符来说,可以先看看js和ts的不同之处:

1
2
3
4
5
function fun(a, b, c) {
console.log(a, b, c);
}
let scores = [1,2,3];
fun(...scores);

对于上面这个例子,在js中是能够正常运行的。但是在typescript中,同样的代码,则会报错。那么在typescript中,如何在函数中使用spread操作符呢?稍作修改,如下所示:

1
2
3
4
5
function fun(a, b, c) {
console.log(a, b, c);
}
let scores: [number, number, number] = [1,2,3];
fun(...scores);

12.empty tuple,如果一个tuple被声明为空的话,那么便不可以具备值,如下所示:

1
2
3
4
5
6
7
8
9
type EmptyTuple = [];
let empty: EmptyTuple = []; // ok
let notEmpty: EmptyTuple = ['str']; // 报错

// tuple的这项特性可以用在union type上面,如下所示
type Less3 = [] | [number] | [number, number] | [number, number, number];
const one: Less3 = [1];
const zero: Less3 = [];
const err: Less3 = [1, 1, 1, 1]; // 报错

13.tuple之optional tuple elements:

需要注意的是,可选参数需要放在最后面。可选参数后面要是有非可选参数的话,那么将会发生错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Scores = [number, number?, number?];
let one: Scores = [1];
let two: Scores = [1,2];
let four: Scores = [1,2,3,4]; // 报错

// 可选参数的后面不能够出现非可选参数
type Scores = [number?, number?, number]; // 报错

// 可选参数的tuple在函数中的作用
function logScores(...scores: Scores) {
console.log(scores);
}
logScores(1); // 输出 [1]

14.unknown type:在typescript3出来之前,对于不确定的属性一般都是使用any来定义变量的,但是如果使用了any的话,那么将用不上typescript的类型检查了。所以使用any是不到万不得已尽量是不要使用的,但是typescript3的unknown类型,在带来了不确定类型的场景使用下,还带来了类型检查,因此建议使用unknown类型替代any。

一个应用场景,利用unknown type来实现type guard。(测试下来好像和unknown没什么用,都是type guard的功劳)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const typeCheck = (obj: any): obj is { firstName: string; age: number } => {
return 'firstName' in obj && 'age' in obj;
}

function log(param: unknown): void { // 换成any好像也有用,所以type guard才是大功臣
if(typeCheck(param)) {
console.log(param.name, param.age); // 在param.name那里会有报错提示
}
}

// 对于上面的typeCheck函数,也可以重写成下面这样方便理解
type TypeA = { firstName: string; age: number };
const typeCheck = (obj: any): obj is TypeA => {
return 'firstName' in obj && 'age' in obj;
}

15.Type narrowing with a type assertion:好像也和unknown没什么关系,还是多亏了类型断言。如下所示:

1
2
3
4
type Big = { name: string; age: number };
function log(obj: unknown): void { // 换成any也行
console.log((obj as Big).firstName); // 报错
}