'this' не определено в методах класса JavaScript

86

Я новичок в JavaScript. Новое, поскольку все, что я действительно сделал с ним, - это доработка существующего кода и написание небольших фрагментов jQuery.

Сейчас я пытаюсь написать «класс» с атрибутами и методами, но у меня проблемы с методами. Мой код:

function Request(destination, stay_open) {
    this.state = "ready";
    this.xhr = null;
    this.destination = destination;
    this.stay_open = stay_open;

    this.open = function(data) {
        this.xhr = $.ajax({
            url: destination,
            success: this.handle_response,
            error: this.handle_failure,
            timeout: 100000000,
            data: data,
            dataType: 'json',
        });
    };

    /* snip... */

}

Request.prototype.start = function() {
    if( this.stay_open == true ) {
        this.open({msg: 'listen'});
    } else {

    }
};
//all console.log's omitted

Проблема заключается в том , в Request.prototype.start, thisне определено и , таким образом, если Равняется заявление к ложным. Что я здесь делаю не так?

Карсон Майерс
источник
Есть ли причина , у вас есть startв prototype?
xj9
Что Request.prototypeнастроено?
Мэтт Болл
У меня здесь был аналогичный вопрос: stackoverflow.com/questions/3198264/…, в котором есть куча полезных ссылок. Суть в том, что thisв JavaScript нет постоянной ссылки на «владельца» вызываемой прототипной функции, как это было бы в большинстве объектно-ориентированных языков, таких как Java.
Марк Боллинджер,
1
@Matt: Request - это функция-конструктор. Request.prototype по умолчанию new Object(). Все, что вы добавляете к нему, автоматически становится свойствами объектов, созданных с помощью new Request().
Chetan S
@Matt Ball Request.prototype- это то место, откуда Requestнаследуются экземпляры . В данном случае это, вероятно, Functionили Object.
xj9

Ответы:

68

Как вы вызываете функцию запуска?

Это должно работать ( новое - это ключ)

var o = new Request(destination, stay_open);
o.start();

Если вы прямо назовете его like Request.prototype.start(), thisбудет ссылаться на глобальный контекст ( windowв браузерах).

Кроме того, если thisне определено, это приведет к ошибке. Выражение if не возвращает false.

Обновление : thisобъект устанавливается не на основе объявления, а путем вызова . Это означает, что если вы присваиваете свойство функции переменной, такой как x = o.startи call x(), thisвнутри start больше не ссылается на o. Вот что происходит, когда вы это делаете setTimeout. Чтобы заставить его работать, сделайте следующее:

 var o = new Request(...);
 setTimeout(function() { o.start(); }, 1000);
Четан С
источник
Я использую setTimeout:var listen = new Request(destination, stay_open); setTimeout(listen.start, 500);
Carson Myers,
Это спасло мне жизнь, когда я пытался понять, почему функция, которую я передавал для выражения basicAuth, не работала с тем же результатом.
Эдисон Спенсер
Или сделай o.start.bind(o). Почему не x = o.start; x()работает?
theonlygusti
.bind()возвращает новую функцию, не работающую на месте. Так что тебе придется делать x = o.start.bind(o); x()илиo.start = o.start.bind(o); x=o.start; x()
ceedob
39

Я просто хотел указать, что иногда эта ошибка возникает из-за того, что функция использовалась как функция высокого порядка (передана в качестве аргумента), а затем область видимости thisтерялась. В таких случаях я бы рекомендовал передать такую ​​функцию привязанной к this. Например

this.myFunction.bind(this);
EliuX
источник
3
Хорошее объяснение !! Это было именно то, что я искал.
vandersondf
1
Невозможно сказать, сколько головной боли вы только что спасли меня
Native Coder
Почему thisтеряется размах ?
theonlygusti
17

ООП в JavaScript немного напуган (или сильно), и к нему нужно привыкнуть. Первое, что вам нужно иметь в виду, это то, что классов нет, и мышление в терминах классов может сбить вас с толку. А чтобы использовать метод, прикрепленный к конструктору (эквивалент определения класса в JavaScript), вам необходимо создать экземпляр вашего объекта. Например:

Ninja = function (name) {
    this.name = name;
};
aNinja = new Ninja('foxy');
aNinja.name; //-> 'foxy'

enemyNinja = new Ninja('boggis');
enemyNinja.name; //=> 'boggis'

Обратите внимание, что Ninjaэкземпляры имеют одинаковые свойства, но aNinjaне могут получить доступ к свойствам enemyNinja. (Эта часть должна быть очень простой / понятной) Все становится немного по-другому, когда вы начинаете добавлять что-то в prototype:

Ninja.prototype.jump = function () {
   return this.name + ' jumped!';
};
Ninja.prototype.jump(); //-> Error.
aNinja.jump(); //-> 'foxy jumped!'
enemyNinja.jump(); //-> 'boggis jumped!'

Вызов этого напрямую вызовет ошибку, потому что thisуказывает только на правильный объект (ваш «Класс»), когда windowсоздается экземпляр Конструктора (в противном случае он указывает на глобальный объект в браузере)

xj9
источник
6

В ES2015, также известном как ES6, classэто синтаксический сахар для functions.

Если вы хотите принудительно установить контекст, thisвы можете использовать bind()метод. Как указал @chetan, при вызове вы также можете установить контекст! Посмотрите пример ниже:

class Form extends React.Component {
constructor() {
    super();
  }
  handleChange(e) {
    switch (e.target.id) {
      case 'owner':
        this.setState({owner: e.target.value});
        break;
      default:
    }
  }
  render() {
    return (
      <form onSubmit={this.handleNewCodeBlock}>
        <p>Owner:</p> <input onChange={this.handleChange.bind(this)} />
      </form>
    );
  }
}

Здесь мы вынуждены контекст внутри handleChange()к Form.

Нитин
источник
6
Вы должны привязать функцию к this в конструкторе. В противном случае вы привязываете его каждый раз, когда он renderвызывается, а не один раз при создании экземпляра класса. Кроме того, большая часть вашего примера не имеет отношения к вопросу.
erich2k8
Или используйте синтаксис стрелки при определенииhandleChange()
Nitin
0

На этот вопрос был дан ответ, но, возможно, сюда придет кто-то другой.

У меня также была проблема с thisundefined, когда я глупо пытался деструктурировать методы класса при его инициализации:

import MyClass from "./myClass"

// 'this' is not defined here:
const { aMethod } = new MyClass()
aMethod() // error: 'this' is not defined

// So instead, init as you would normally:
const myClass = new MyClass()
myClass.aMethod() // OK

Оли
источник
0

Используйте функцию стрелки:

Request.prototype.start = () => {
    if( this.stay_open == true ) {
        this.open({msg: 'listen'});
    } else {

    }
};
Kasra
источник