Свойство объекта Javascript сообщается как неопределенное методом, вызываемым с помощью setInterval - PullRequest
2 голосов
/ 15 мая 2019

Я пытаюсь обернуть голову вокруг Javascript и хочу создать простой веб-апплет, который можно включать и отключать с помощью кнопок, и, когда он включен, рисует вещи.Чтобы написать чистый (er) код, я хотел использовать объект для этого.Настройка страницы с кнопками -

<div>
    <input type="button" name="runbutton"   value="Run"   onclick="game.run();"/>
    <input type="button" name="resetbutton" value="Reset" onclick="game.reset();"/>
</div>

<script>
    //...
</script>

, а код javascript -

function Game() {
    this.runs = false;
    this.run = function() {console.log('run...'); this.runs = true;};
    this.reset = function() {console.log('reset...'); this.runs = false;};
    this.update = function() {console.log('updating... runs:', this.runs);};
};
var game = new Game();
game.reset();
setInterval(game.update, 300);

Таким образом, это определение объекта (Game) с одним экземпляром (game) с одним логическим значениемсвойство (прогоны) и три метода.Тот, который запускает его, тот, который перестает работать, и метод update (), который сообщает, выполняется ли он.Функция update () повторяется каждые 300 мс с использованием setInterval.

Проблема : журналы консоли из update () сообщают значение this.runs как неопределенное, а не как false или true.Когда я открываю консоль и ставлю ее на паузу, чтобы проверить переменные, она сообщает game.runs как false или true.Кроме того, когда я добавляю вызовы console.log () в run () и reset (), сообщая о значении this.runs до и после его установки, кажется, что он сообщает true и false правильно.Таким образом, проблема, кажется, где-то в update ().Это как если бы он использовал неправильное «это».Может быть, setInterval нельзя использовать в методах?

Я пробовал два других синтаксиса для кода, но они, похоже, имеют ту же самую проблему:

var game = {
    runs: false,
    run: function() {console.log('run...'); this.runs = true;},
    reset: function() {console.log('reset...'); this.runs = false;},
    update: function() {console.log('update... runs:', this.runs);}
};
game.reset();
setInterval(game.update, 300);

и версию, которая устанавливает setInterval в пределахобъект:

var game = {
    runs: false,
    i: undefined,
    run: function() {console.log('run...'); this.runs = true; this.i = setInterval(this.update, 300);},
    reset: function() {console.log('reset...'); this.runs = false; clearInterval(this.i);},
    update: function() {console.log('update... runs:', this.runs);}
};
game.reset();

Та же проблема.

Что происходит?Почему update () сообщает об этом .runs как неопределенном?Правильно ли я понимаю, что «this» в методах действительно относится к экземпляру игры во всех случаях?Разве я не должен использовать setInterval для метода и вместо этого вызывать глобальную функцию?

Ответы [ 3 ]

1 голос
/ 15 мая 2019

В JavaScript правила для this несколько сложны;уместным является то, что функция без стрелки, сохраненная в свойстве объекта, может назначить this объекту, если вызывается как метод.Давайте разберем это:

  • game.update - это свойство объекта game,
  • Содержит функцию без стрелки,
  • Вызываетсякак метод ... ❌

Что означает "вызванный как метод"?Это означает, что вы вызываете функцию с синтаксисом object.property, например: game.update(...).

Однако, game.update передается как параметр, где он теряет соединение с game.Ваш код эквивалентен:

var func = game.update;
setInterval(func, 300);

, где setTimeout просто вызовет func().Это означает, что game.update вызывается как функция, а не как метод, и this не будет установлен на game при его вызове.

Типичные обходные пути:

  • привязка приемника к функции.Это еще один способ установить this помимо вызова метода, описанного выше: если функция связана с объектом-получателем, она всегда будет устанавливать this при вызове.Вы могли бы написать это как:

    setInterval(game.update.bind(game), 300)
    

    Вариант этого, часто используемого в React, это явное связывание функций с получателем в месте определения:

    this.update = function() {console.log('updating... runs:', this.runs);};
    this.update = this.update.bind(this);
    
  • явно использовать вызов метода, используя один из следующих способов:

    setInterval(() => game.update(), 300);
    setInterval(function() { game.update(); }, 300);
    
  • let this может быть определено лексически с помощью функций стрелок.Поскольку this является игровым объектом в точке, в которой определены функции, превращение их в функции стрелок всегда будет устанавливать this для этого игрового объекта.Это требует изменения в точке определения, а не в точке вызова:

    this.update = () => {console.log('updating... runs:', this.runs);};
    
0 голосов
/ 15 мая 2019

Поскольку контекст this больше не является game, game.update вызывается как обратный вызов setInterval, если вы используете функции со стрелками, это решит проблему.

function Game() {
    self = this
    this.runs = false;
    this.run = function() {console.log('run...'); self.runs = true;};
    this.reset = function() {console.log('reset...'); self.runs = false;};
    // Using arrow function () => {} instead of normal function
    this.update = () => {console.log('updating... runs:', self.runs);};
};
var game = new Game();
game.reset();
setInterval(game.update, 300);

Подробнее о функциях стрелок и здесь здесь

0 голосов
/ 15 мая 2019

когда вы определяете внутри функции, используя этот синтаксис: function() {}, тогда эта функция будет иметь свой собственный this, поэтому this.runs будет неопределенным, если вы хотите, чтобы это был объект родительской функции, у вас есть два опции:

OPTION1: определить внутренние функции как функции стрелок:

function Game() {
    this.runs = false;
    this.run = () => {console.log('run...'); this.runs = true;};
    this.reset = () => {console.log('reset...'); this.runs = false;};
    this.update = () => {console.log('updating... runs:', this.runs);};
};
var game = new Game();
game.reset();
setInterval(game.update, 300);
<div>
    <input type="button" name="runbutton"   value="Run"   onclick="game.run();"/>
    <input type="button" name="resetbutton" value="Reset" onclick="game.reset();"/>
</div>

ОПЦИЯ2: сохранить родительскую функцию this как переменную

function Game() {
    self = this
    this.runs = false;
    this.run = function() {console.log('run...'); self.runs = true;};
    this.reset = function() {console.log('reset...'); self.runs = false;};
    this.update = function() {console.log('updating... runs:', self.runs);};
};
var game = new Game();
game.reset();
setInterval(game.update, 300);
<div>
    <input type="button" name="runbutton"   value="Run"   onclick="game.run();"/>
    <input type="button" name="resetbutton" value="Reset" onclick="game.reset();"/>
</div>
...