Gowhich

Durban's Blog

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.4

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

函数

this

学习如何在JavaScript里正确使用this就好比一场成年礼。 由于TypeScript是JavaScript的超集,TypeScript程序员也需要弄清 this工作机制并且当有bug的时候能够找出错误所在。 幸运的是,TypeScript能通知你错误地使用了 this的地方。 如果你想了解JavaScript里的 this是如何工作的,那么首先阅读Yehuda Katz写的Understanding JavaScript Function Invocation and “this”。 Yehuda的文章详细的阐述了 this的内部工作原理,因此这里只做简单介绍。

this参数在回调函数里

继续上篇文章【TypeScript基础入门 - 函数 - this(二)

我们也看到过在回调函数里this报错的情况,当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候,它们会被当成一个普通函数调用, this将为undefined。 稍做改动,你就可以通过 this参数来避免错误。 首先,库函数的作者要指定 this的类型,如下实例

1
2
3
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}

this: void表示addClickListener期望onclick是一个不需要此类型的函数。
其次,用这个注释你的调用代码,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface UIElement {
addClickListener(onclick: (this: void, e: Error) => void): void;
}

class Handler {
info: string;
onClickBad(this: Handler, e: Error) {
// oops, used this here. using this callback would crash at runtime
this.info = e.message;
}
}
let h = new Handler();
let uiElement: UIElement = {
addClickListener(onclick: (this: void, e: Error) => void) {
// do something
}
};

uiElement.addClickListener(h.onClickBad); // 这里会报错

指定了this类型后,显式声明onClickBad必须在Handler的实例上调用。 然后TypeScript会检测到 addClickListener要求函数带有this: void。 我们添加另外一个函数做下对比,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface UIElement {
addClickListener(onclick: (this: void, e: Error) => void): void;
}

class Handler {
info: string;
onClickBad(this: Handler, e: Error) {
this.info = e.message;
}
onClickGood(this: void, e: Error) {
console.log('点击了!');
}
}
let h = new Handler();
let uiElement: UIElement = {
addClickListener(onclick: (this: void, e: Error) => void) {
// do something
}
};

uiElement.addClickListener(h.onClickGood);

通过将h.onClickBad更换为h.onClickGood,就能正常调用。
因为onClickGood指定了this类型为void,因此传递addClickListener是合法的。 当然了,这也意味着不能使用 this.info. 如果你两者都想要,你不得不使用箭头函数了,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface UIElement {
addClickListener(onclick: (this: void, e: Error) => void): void;
}

class Handler {
info: string;
onClickGood = (e: Error) => { this.info = e.message }
}

let h = new Handler();
let uiElement: UIElement = {
addClickListener(onclick: (this: void, e: Error) => void) {
// do something
}
};

uiElement.addClickListener(h.onClickGood);

这是可行的因为箭头函数不会捕获this,所以你总是可以把它们传给期望this: void的函数。 缺点是每个 Handler对象都会创建一个箭头函数。 另一方面,方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.5

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.3

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

函数

this

学习如何在JavaScript里正确使用this就好比一场成年礼。 由于TypeScript是JavaScript的超集,TypeScript程序员也需要弄清 this工作机制并且当有bug的时候能够找出错误所在。 幸运的是,TypeScript能通知你错误地使用了 this的地方。 如果你想了解JavaScript里的 this是如何工作的,那么首先阅读Yehuda Katz写的Understanding JavaScript Function Invocation and “this”。 Yehuda的文章详细的阐述了 this的内部工作原理,因此这里只做简单介绍。

this参数

继续上篇文章【TypeScript基础入门 - 函数 - this(一)

this.suits[pickedSuit]的类型依旧为any。 这是因为 this来自对象字面量里的函数表达式。 修改的方法是,提供一个显式的 this参数。 this参数是个假的参数,它出现在参数列表的最前面,如下

1
2
3
function f(this: void) {
// 确保`this`在这个独立功能中无法使用
}

我们添加一些接口,Card 和 Deck,让类型重用能够变得清晰简单些,代码如下

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
interface Card {
suit: string;
card: number;
}

interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}

let deck: Deck = {
suits: [
'hearts',
'spades',
'clubs',
'diamods'
],
cards: Array(52),
createCardPicker: function (this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);

return {
suit: this.suits[pickedCard],
card: pickedCard % 13,
}
}
}

}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

运行后得到的结果类似如下

1
2
$ npx ts-node src/function_5.ts
card: 3 of diamods

现在TypeScript知道createCardPicker期望在某个Deck对象上调用。 也就是说 this是Deck类型的,而非any,因此–noImplicitThis不会报错了。

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.4

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.2

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

函数

this

学习如何在JavaScript里正确使用this就好比一场成年礼。 由于TypeScript是JavaScript的超集,TypeScript程序员也需要弄清 this工作机制并且当有bug的时候能够找出错误所在。 幸运的是,TypeScript能通知你错误地使用了 this的地方。 如果你想了解JavaScript里的 this是如何工作的,那么首先阅读Yehuda Katz写的Understanding JavaScript Function Invocation and “this”。 Yehuda的文章详细的阐述了 this的内部工作原理,因此这里只做简单介绍。

this和箭头函数

JavaScript里,this的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。 但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。

下面看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let deck = {
suits: [
'hearts',
'spades',
'clubs',
'diamods'
],
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);

可以看到createCardPicker是个函数,并且它又返回了一个函数。 如果我们尝试运行这个程序,会得到如下类似错误提示

1
2
$ npx ts-node src/function_4.ts
Cannot read property '2' of undefined

因为 createCardPicker返回的函数里的this被设置成了window而不是deck对象。 因为我们只是独立的调用了 cardPicker()。 顶级的非方法式调用会将 this视为window。 (注意:在严格模式下, this为undefined而不是window)。为了解决这个问题,我们可以在函数被返回时就绑好正确的this。 这样的话,无论之后怎么使用它,都会引用绑定的’deck’对象。 我们需要改变函数表达式来使用ECMAScript 6箭头语法。 箭头函数能保存函数创建时的 this值,而不是调用时的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let deck = {
suits: [
'hearts',
'spades',
'clubs',
'diamods'
],
cards: Array(52),
createCardPicker: function() {
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();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);

运行后得到的结果如下

1
2
$ npx ts-node src/function_4.ts
card: 10 of hearts

TypeScript会警告你犯了一个错误,如果你给编译器设置了–noImplicitThis标记。 它会指出 this.suits[pickedSuit]里的this的类型为any。

这个是官方说的,但是实际上运行的时候,并没有什么警告的信息

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.3

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.1

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

函数

剩余参数

必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在JavaScript里,你可以使用 arguments来访问所有传入的参数。在TypeScript里,你可以把所有参数收集到一个变量里:

1
2
3
4
5
6
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ")
}

let aName = buildName("Lili", "John", "David", "Durban");
console.log(aName);

运行后得到的结果如下

1
2
$ npx ts-node src/function_3.ts
Lili John David Durban

剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号(…)后面给定的名字,你可以在函数体内使用这个数组。这个省略号也会在带有剩余参数的函数类型定义上使用到:

1
2
3
4
5
6
7
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}

let buildNameFunc: (fname: string, ...rest: string[]) => string = buildName;

console.log(buildNameFunc("John", "Julia", "July"));

运行后得到的结果如下

1
2
$ npx ts-node src/function_3.ts
Lili John David Durban

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.2

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.0

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

函数

可选参数和默认参数

TypeScript里的每个函数参数都是必须的。 这不是指不能传递 null或undefined作为参数,而是说编译器检查用户是否为每个参数都传入了值。 编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。如下实例演示

1
2
3
4
5
6
7
8
9
10
function buildName(firstName: string, lastName: string) {
return firstName + ' ' + lastName
}

// 错误演示
buildName("firstName")
// 错误演示
buildName("firstName", "lastName", "lastName")
// 正确演示
buildName("firstName", "lastName")

JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。 在TypeScript里我们可以在参数名旁使用 ?实现可选参数的功能。 比如,我们想让last name是可选的:

1
2
3
4
5
6
7
8
9
10
function buildName(firstName: string, lastName?: string) {
return firstName + ' ' + lastName
}

// 错误演示
buildName("firstName", "lastName", "lastName")
// 正确演示
buildName("firstName")
// 正确演示
buildName("firstName", "lastName")

可选参数必须跟在必须参数后面。 如果上例我们想让first name是可选的,那么就必须调整它们的位置,把first name放在后面。在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是undefined时。 它们叫做有默认初始化值的参数。 让我们修改上例,把last name的默认值设置为”Smith”。

1
2
3
4
5
6
7
8
9
10
11
12
function buildName(firstName: string, lastName="Smith") {
return firstName + ' ' + lastName
}

// 正确演示
buildName("A")
// 正确演示
buildName("A", undefined)
// 错误演示
buildName("firstName", "lastName", "lastName")
// 正确演示
buildName("firstName", "lastName")

在所有必须参数后面的带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略。 也就是说可选参数与末尾的默认参数共享参数类型。

1
2
3
function buildName(firstName: string, lastName="Smith") {
return firstName + ' ' + lastName
}

1
2
3
function buildName(firstName: string, lastName?: string) {
return firstName + ' ' + lastName
}

共享同样的类型(firstName: string, lastName?: string) => string。 默认参数的默认值消失了,只保留了它是一个可选参数的信息。与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined值来获得默认值。 例如,我们重写最后一个例子,让 firstName是带默认值的参数:

1
2
3
4
5
6
7
8
9
10
11
12
function buildName(firstName="Durban", lastName: string) {
return firstName + ' ' + lastName
}

// 错误演示
buildName("A")
// 正确演示
buildName(undefined, "A")
// 错误演示
buildName("firstName", "lastName", "lastName")
// 正确演示
buildName("firstName", "lastName")

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.1

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.6

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

函数

介绍

函数是JavaScript应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义 行为的地方。 TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用。说实话我都不想看这块的东西,函数嘛是个写程序的都会写,但是为了追求整体及探索新的知识,没准有意外发现,还是要学习下。

函数

和JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数。 你可以随意选择适合应用程序的方式,不论是定义一系列API函数还是只使用一次的函数。通过下面的例子可以迅速回想起这两种JavaScript中的函数:

1
2
3
4
5
function add(x, y) {
return x + y
}

let addFunc = function(x, y) { return x + y }

在JavaScript里,函数可以使用函数体外部的变量。 当函数这么做时,我们说它‘捕获’了这些变量。
至于为什么可以这样做以及其中的利弊超出了本文的范围,但是深刻理解这个机制对学习JavaScript和TypeScript会很有帮助。

1
2
3
4
let z = 10;
function addTo(x, y) {
return x + y + z;
}

函数类型

为函数定义类型
让我们为上面那个函数添加类型

1
2
3
4
5
function add(x: number, y:number): number {
return x + y;
}

let addFunc = function (x: number, y: number): number { return x + y }

我们可以给每个参数添加类型之后再为函数本身添加返回值类型。 TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。

书写完整函数类型

现在我们已经为函数指定了类型,下面让我们写出函数的完整类型。

1
let addFunc: (x: number, y:number) => number = function(x: number, y: number): number { return x + y }

函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的。 我们以参数列表的形式写出参数类型,为每个参数指定一个名字和类型。 这个名字只是为了增加可读性。 我们也可以这么写

1
let addFunc: (baseValue: number, increment: number) => number = function (x: number, y: number): number { return x + y }

只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。

第二部分是返回值类型。 对于返回值,我们在函数和返回值类型之前使用( =>)符号,使之清晰明了。 如之前提到的,返回值类型是函数类型的必要部分,如果函数没有返回任何值,你也必须指定返回值类型为 void而不能留空。

函数的类型只是由参数类型和返回值组成的。 函数中使用的捕获变量不会体现在类型里。 实际上,这些变量是函数的隐藏状态并不是组成API的一部分。

推断类型

尝试这个例子的时候,你会发现如果你在赋值语句的一边指定了类型但是另一边没有类型的话,TypeScript编译器会自动识别出类型:

1
let addFunc = function(x: number, y: number): number { return x + y }
1
let addFunc: (baseValue: number, increment: number) => number = function(x, y) { return  x + y }

这叫做“按上下文归类”,是类型推论的一种。 它帮助我们更好地为程序指定类型。

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.2.0

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.5

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

高级技巧

构造函数

当你在TypeScript里声明了一个类的时候,实际上同时声明了很多东西。 首先就是类的 实例的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}

greet() {
return "Hello, " + this.greeting;
}
}

let greeter: Greeter;
greeter = new Greeter('Gowhich');
console.log(greeter.greet());

运行后得到如下结果

1
2
$ npx ts-node src/classes_7.ts
Hello, Gowhich

这里,我们写了 let greeter: Greeter,意思是 Greeter类的实例的类型是 Greeter。 这对于用过其它面向对象语言的程序员来讲已经是老习惯了。

我们也创建了一个叫做 构造函数的值。 这个函数会在我们使用 new创建类实例的时候被调用。 下面我们来看看,上面的代码被编译成JavaScript后是什么样子的,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
var Greeter = /** @class */ (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
}());
var greeter;
greeter = new Greeter('Gowhich');
console.log(greeter.greet());

上面的代码里, var Greeter将被赋值为构造函数。 当我们调用 new并执行了这个函数后,便会得到一个类的实例。 这个构造函数也包含了类的所有静态属性。 换个角度说,我们可以认为类具有 实例部分与 静态部分这两个部分。让我们稍微改写一下这个例子,看看它们之间的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Greeter {
static standardGreeting: string = 'Hello, World';
greeting: string;
greet() {
if (this.greeting) {
return "Hello, "+this.greeting;
} else {
return Greeter.standardGreeting;
}
}
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = 'Hello, other';

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

运行后得到的结果如下

1
2
3
$ npx ts-node src/classes_7.ts
Hello, World
Hello, other

这个例子里, greeter1与之前看到的一样。 我们实例化 Greeter类,并使用这个对象。 与我们之前看到的一样。

再之后,我们直接使用类。 我们创建了一个叫做 greeterMaker的变量。 这个变量保存了这个类或者说保存了类构造函数。 然后我们使用 typeof Greeter,意思是取Greeter类的类型,而不是实例的类型。 或者更确切的说,”告诉我 Greeter标识符的类型”,也就是构造函数的类型。 这个类型包含了类的所有静态成员和构造函数。 之后,就和前面一样,我们在 greeterMaker上使用 new,创建 Greeter的实例。

把类当做接口使用

类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point {
x: number;
y: number;
}

interface Point3D extends Point {
z: number;
}

let point3d: Point3D = {
x: 1,
y: 2,
z: 3,
}

console.log('point3d = ', point3d);

运行后得到的结果如下

1
2
$ npx ts-node src/classes_7.ts
point3d = { x: 1, y: 2, z: 3 }

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.6

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.4

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。具体例子如下

1
2
3
4
5
abstract class Animal {
abstract makeSount(): void; move(): void {
console.log('我在移动');
}
}

抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符。具体示例如下

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
abstract class Department {
constructor(public name: string) {

}

printName(): void {
console.log("部门名称:" + this.name);
}

abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {
constructor() {
super("会计和审计"); // 在派生类中必须调用super()
}

printMeeting(): void {
console.log('会计部每个星期一上午10点开会');
}

generateReports(): void {
console.log('生成会议报告');
}
}

let department: Department; // 允许创建一个对抽象类型的引用

// department = new Department(); // 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
// department.generateReports(); // 此方法不能调用,因为在声明的抽象类中不存在

运行后的结果如下

1
2
3
$ npx ts-node src/classes_6.ts
部门名称:会计和审计
会计部每个星期一上午10点开会

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.5

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.3

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

静态属性

到目前为止,只学习了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用 static定义 origin,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在 origin前面加上类名。 如同在实例属性上使用 this.前缀来访问属性一样,这里我们使用 Grid.来访问静态属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Grid {
constructor(public scale: number) { }

static origin = {
x:0,
y:0,
}

calculateDistanceFromOrigin(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(2.0)

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));

运行后结果如下

1
2
14.142135623730951
7.0710678118654755

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.4

项目实践仓库

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.2

为了保证后面的学习演示需要安装下ts-node,这样后面的每个操作都能直接运行看到输出的结果。

1
npm install -D ts-node

后面自己在练习的时候可以这样使用

1
npx ts-node 脚本路径

存取器

TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。下面来看如何把一个简单的类改写成使用 get和 set。 首先,我们从一个没有使用存取器的例子开始

1
2
3
4
5
6
7
8
9
10
class Employee {
fullName: string
}

let employee = new Employee();
employee.fullName = "durban zhang";

if (employee.fullName) {
console.log(employee.fullName);
}

运行后结果如下

1
2
$ npx ts-node src/classes_4.ts
durban zhang

我们可以随意的设置 fullName,这是非常方便的,但是这也可能会带来麻烦。

下面这个版本里,我们先检查用户密码是否正确,然后再允许其修改员工信息。 我们把对 fullName的直接访问改成了可以检查密码的 set方法。 我们也加了一个 get方法,让上面的例子仍然可以工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let passcode = 'pass';
class Employee {
private _fullName: string;

get fullName(): string {
return this._fullName;
}

set fullName(name: string) {
if (passcode && passcode === 'pass') {
this._fullName = name;
} else {
console.log('授权失败');
}
}
}

let employee = new Employee();
employee.fullName = "durban zhang";

if (employee.fullName) {
console.log(employee.fullName);
}

运行后结果如下

1
2
$ npx ts-node src/classes_4.ts
durban zhang

可以修改一下密码,来验证一下存取器是否是工作的。当密码不对时,会提示我们没有权限去修改员工。

对于存取器有下面几点需要注意的:

首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。

本实例结束实践项目地址

1
2
https://github.com/durban89/typescript_demo.git
tag: 1.1.3
0%