Typescript 中 "this" 的意外值
Unexpected value of "this" in Typescript
我正在关注 Phaser tutorial 制作一个 Flappy Bird 克隆。但是在 create
方法内部,this
的值正在从 FlappyClone
对象更改:
到另一个包含大量 Phaser
东西的对象:
这导致对 this.createBird
的调用失败,因为 create
中的 this
值没有 createBird
方法。
为什么 this
会发生变化?由于 create
是一个 class 方法,难道 this
不应该引用 class 实例吗?
而且 Phaser 可能不是用不同的对象调用方法,因为 this 教程显示 this
在方法内部使用,并引用实例成员。
完整代码(app.ts):
class FlappyClone {
private GAME_WIDTH: number = 800;
private GAME_HEIGHT: number = 600;
private game: Phaser.Game;
private bird: Phaser.Sprite;
private startState = { preload: this.preload, create: this.create, update: this.update };
constructor() {
this.game = new Phaser.Game(this.GAME_WIDTH, this.GAME_HEIGHT, Phaser.AUTO, "content", this.startState);
this.game.state.add('start', this.startState, true);
}
preload() {
this.game.load.image('bird', 'assets/bird.png');
this.game.load.image('pipePiece', 'assets/pipe.png');
this.game.physics.startSystem(Phaser.Physics.ARCADE);
}
create() {
this.bird = this.createBird(this.game.world.centerX, this.game.world.centerY);
let spaceBar = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
spaceBar.onDown.add(() => {
FlappyClone.makeJump(this.bird, 500);
}, this);
}
update() {
if (this.bird.x < 0 || this.bird.y > this.GAME_HEIGHT) {
this.restartGame();
}
}
/*private createPipePiece(): Phaser.Spirit {
var pipePiece = this.game.add.sprite(this.game.
}*/
private createBird(x, y): Phaser.Sprite {
var bird = this.game.add.sprite(x, y, 'bird');
this.game.physics.enable(bird);
bird.body.gravity.y = 500;
return bird;
}
private restartGame() {
this.game.state.start('start');
}
private static makeJump(sprite: Phaser.Sprite, velocity: number) {
sprite.body.velocity.y = -velocity;
}
}
window.onload = () => {
var game = new FlappyClone();
};
shouldn't this refer to the class instance
没有。这是JavaScript。 this
称为 调用上下文 并由 调用者 驱动。
更多
https://www.youtube.com/watch?v=tvocUcbCupA
快速修复
为所有 class 方法使用箭头函数:
https://basarat.gitbooks.io/typescript/content/docs/arrow-functions.html
问题
您有 classic Java脚本问题,称为 不正确的 this
上下文 。
this
keyword in JavaScript 的行为不同于其他语言,如 C# 和 Java。
具体来说,这些引用导致丢失 this
上下文:
private startState = { preload: this.preload, create: this.create, update: this.update };
this
的工作原理
this
关键字,在函数中,确定如下:
* 如果函数是通过调用 .bind
创建的,this
值是提供给 bind
的参数
* 如果函数是通过方法调用 调用的 ,例如expr.func(args)
,那么this
就是expr
* 除此以外
* 如果代码在strict mode,this
就是undefined
* 否则,this
是 window
(在浏览器中)
让我们看看这在实践中是如何工作的:
class Foo {
value = 10;
doSomething() {
// Prints 'undefined', not '10'
console.log(this.value);
}
}
let f = new Foo();
window.setTimeout(f.doSomething, 100);
此代码将打印 undefined
(或者,在严格模式下,抛出异常)。
这是因为我们最终到达了上面决策树的最后一个分支。
调用了 doSomething
函数,该函数不是 bind
调用的结果,也不是在方法语法位置调用的。
我们看不到 setTimeout
的代码来了解它的调用是什么样的,但我们不需要。
需要注意的是,所有 doSomething
方法都指向 相同的函数对象 。
也就是说:
let f1 = new Foo();
let f2 = new Foo();
// 'true'
console.log(f1.doSomething === f2.doSomething);
我们知道setTimeout
只能看到我们传递给它的函数,所以当它调用那个函数时,
它无法知道要提供哪个 this
。
由于我们 引用 方法而没有 调用 它,this
上下文已经丢失。
红旗
一旦您了解 this
个问题,就很容易发现它们:
class Foo {
value = 10;
method1() {
doSomething(this.method2); // DANGER, method reference without invocation
}
method2() {
console.log(this.value);
}
}
解决方案
这里有几个选项,每个选项都有自己的权衡取舍。
最佳选择取决于从不同调用站点调用相关方法的频率。
Class 定义中的箭头函数
不使用正常的方法语法,而是使用 arrow function 来初始化每个实例成员。
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
// OK
console.log(this.status);
}
}
let d = new DemonstrateScopingProblems();
window.setTimeout(d.run); // OK
- Good/bad:这会为 class 的每个实例的每个方法创建一个额外的闭包。如果这个方法通常只用在常规方法调用中,那就太过分了。但是,如果它在回调位置使用很多,class 实例捕获
this
上下文而不是每个调用站点在调用时创建一个新的闭包会更有效。
- 好:外部调用者不可能忘记处理
this
上下文
- 好:TypeScript 中的类型安全
- 好:如果函数有参数则无需额外工作
- 错误:派生的 classes 无法调用使用
super.
以这种方式编写的基础 class 方法
- 不好:哪些方法是 "pre-bound" 哪些方法不是的确切语义在您的 class 和它的消费者之间创建了一个额外的非类型安全契约。
参考站点的函数表达式
出于解释原因,此处显示了一些虚拟参数:
class DemonstrateScopingProblems {
private status = "blah";
public something() {
console.log(this.status);
}
public run(x: any, y: any) {
// OK
console.log(this.status + ': ' + x + ',' + y);
}
}
let d = new DemonstrateScopingProblems();
// With parameters
someCallback((n, m) => d.run(n, m));
// Without parameters
window.setTimeout(() => d.something(), 100);
- Good/bad:与第一种方法
相反memory/performance权衡
- 好:在 TypeScript 中,这具有 100% 的类型安全性
- 好:适用于 ECMAScript 3
- 很好:您只需输入一次实例名称
- 不好:您必须输入两次参数
- 不好:不能轻易使用可变参数
我正在关注 Phaser tutorial 制作一个 Flappy Bird 克隆。但是在 create
方法内部,this
的值正在从 FlappyClone
对象更改:
到另一个包含大量 Phaser
东西的对象:
这导致对 this.createBird
的调用失败,因为 create
中的 this
值没有 createBird
方法。
为什么 this
会发生变化?由于 create
是一个 class 方法,难道 this
不应该引用 class 实例吗?
而且 Phaser 可能不是用不同的对象调用方法,因为 this 教程显示 this
在方法内部使用,并引用实例成员。
完整代码(app.ts):
class FlappyClone {
private GAME_WIDTH: number = 800;
private GAME_HEIGHT: number = 600;
private game: Phaser.Game;
private bird: Phaser.Sprite;
private startState = { preload: this.preload, create: this.create, update: this.update };
constructor() {
this.game = new Phaser.Game(this.GAME_WIDTH, this.GAME_HEIGHT, Phaser.AUTO, "content", this.startState);
this.game.state.add('start', this.startState, true);
}
preload() {
this.game.load.image('bird', 'assets/bird.png');
this.game.load.image('pipePiece', 'assets/pipe.png');
this.game.physics.startSystem(Phaser.Physics.ARCADE);
}
create() {
this.bird = this.createBird(this.game.world.centerX, this.game.world.centerY);
let spaceBar = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
spaceBar.onDown.add(() => {
FlappyClone.makeJump(this.bird, 500);
}, this);
}
update() {
if (this.bird.x < 0 || this.bird.y > this.GAME_HEIGHT) {
this.restartGame();
}
}
/*private createPipePiece(): Phaser.Spirit {
var pipePiece = this.game.add.sprite(this.game.
}*/
private createBird(x, y): Phaser.Sprite {
var bird = this.game.add.sprite(x, y, 'bird');
this.game.physics.enable(bird);
bird.body.gravity.y = 500;
return bird;
}
private restartGame() {
this.game.state.start('start');
}
private static makeJump(sprite: Phaser.Sprite, velocity: number) {
sprite.body.velocity.y = -velocity;
}
}
window.onload = () => {
var game = new FlappyClone();
};
shouldn't this refer to the class instance
没有。这是JavaScript。 this
称为 调用上下文 并由 调用者 驱动。
更多
https://www.youtube.com/watch?v=tvocUcbCupA
快速修复
为所有 class 方法使用箭头函数:
https://basarat.gitbooks.io/typescript/content/docs/arrow-functions.html
问题
您有 classic Java脚本问题,称为 不正确的 this
上下文 。
this
keyword in JavaScript 的行为不同于其他语言,如 C# 和 Java。
具体来说,这些引用导致丢失 this
上下文:
private startState = { preload: this.preload, create: this.create, update: this.update };
this
的工作原理
this
关键字,在函数中,确定如下:
* 如果函数是通过调用 .bind
创建的,this
值是提供给 bind
的参数
* 如果函数是通过方法调用 调用的 ,例如expr.func(args)
,那么this
就是expr
* 除此以外
* 如果代码在strict mode,this
就是undefined
* 否则,this
是 window
(在浏览器中)
让我们看看这在实践中是如何工作的:
class Foo {
value = 10;
doSomething() {
// Prints 'undefined', not '10'
console.log(this.value);
}
}
let f = new Foo();
window.setTimeout(f.doSomething, 100);
此代码将打印 undefined
(或者,在严格模式下,抛出异常)。
这是因为我们最终到达了上面决策树的最后一个分支。
调用了 doSomething
函数,该函数不是 bind
调用的结果,也不是在方法语法位置调用的。
我们看不到 setTimeout
的代码来了解它的调用是什么样的,但我们不需要。
需要注意的是,所有 doSomething
方法都指向 相同的函数对象 。
也就是说:
let f1 = new Foo();
let f2 = new Foo();
// 'true'
console.log(f1.doSomething === f2.doSomething);
我们知道setTimeout
只能看到我们传递给它的函数,所以当它调用那个函数时,
它无法知道要提供哪个 this
。
由于我们 引用 方法而没有 调用 它,this
上下文已经丢失。
红旗
一旦您了解 this
个问题,就很容易发现它们:
class Foo {
value = 10;
method1() {
doSomething(this.method2); // DANGER, method reference without invocation
}
method2() {
console.log(this.value);
}
}
解决方案
这里有几个选项,每个选项都有自己的权衡取舍。 最佳选择取决于从不同调用站点调用相关方法的频率。
Class 定义中的箭头函数
不使用正常的方法语法,而是使用 arrow function 来初始化每个实例成员。
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
// OK
console.log(this.status);
}
}
let d = new DemonstrateScopingProblems();
window.setTimeout(d.run); // OK
- Good/bad:这会为 class 的每个实例的每个方法创建一个额外的闭包。如果这个方法通常只用在常规方法调用中,那就太过分了。但是,如果它在回调位置使用很多,class 实例捕获
this
上下文而不是每个调用站点在调用时创建一个新的闭包会更有效。 - 好:外部调用者不可能忘记处理
this
上下文 - 好:TypeScript 中的类型安全
- 好:如果函数有参数则无需额外工作
- 错误:派生的 classes 无法调用使用
super.
以这种方式编写的基础 class 方法
- 不好:哪些方法是 "pre-bound" 哪些方法不是的确切语义在您的 class 和它的消费者之间创建了一个额外的非类型安全契约。
参考站点的函数表达式
出于解释原因,此处显示了一些虚拟参数:
class DemonstrateScopingProblems {
private status = "blah";
public something() {
console.log(this.status);
}
public run(x: any, y: any) {
// OK
console.log(this.status + ': ' + x + ',' + y);
}
}
let d = new DemonstrateScopingProblems();
// With parameters
someCallback((n, m) => d.run(n, m));
// Without parameters
window.setTimeout(() => d.something(), 100);
- Good/bad:与第一种方法 相反memory/performance权衡
- 好:在 TypeScript 中,这具有 100% 的类型安全性
- 好:适用于 ECMAScript 3
- 很好:您只需输入一次实例名称
- 不好:您必须输入两次参数
- 不好:不能轻易使用可变参数