Что делать, когда «это» теряет контекст

chto delat kogda eto teryaet kontekst

Откройте для себя функциональный JavaScript был назван одним из лучшие новые книги по функциональному программированию от BookAuthority!

Лучший способ избежать this потерять контекст – это не использовать this совсем. Однако это не всегда вариант. Возможно, мы наследуем код, который использует this или мы можем работать с библиотекой, которая использует this.

Литералы объектов, функции конструктора и classстроят объекты на основе прототипа системы. The this Псевдопараметр используется системой-прототипом, чтобы предоставить функциям доступ к другим свойствам объекта.

Давайте рассмотрим некоторые ситуации.

Вложенные функции

this теряет контекст внутри вложенных функций. Рассмотрим следующий код:

class Service {
  constructor(){
    this.numbers = [1,2,3];
    this.token = "token";
  }
  
  doSomething(){
    setTimeout(function doAnotherThing(){
      this.numbers.forEach(function log(number){
      //Cannot read property 'forEach' of undefined
          console.log(number);
          console.log(this.token);
      });
    }, 100);
  }
}

let service = new Service();
service.doSomething();

The doSomething() метод имеет две вложенные функции: doAnotherThing() и log(). Когда service.doSomething() это называется, this теряет контекст во вложенных функциях.

bind()

Одним из способов решения проблемы является с помощью bind(). Посмотрите на следующий код:

doSomething(){
   setTimeout(function doAnotherThing(){
      this.numbers.forEach(function log(number){
         console.log(number);
         console.log(this.token);
      }.bind(this));
    }.bind(this), 100);
  }

bind() создает новую версию функции, которая при вызове имеет this значение уже установлено. Заметим, что нам нужно использовать .bind(this) для каждой вложенной функции.

function doAnotherThing(){ /*…*/}.bind(this) создает версию doAnotherThing() что занимает this значение от doSomething().

это я

Другой вариант – объявить и использовать новую переменную that/self в котором сохраняется значение this от doSomething() метод.

Смотрите код ниже:

doSomething(){
   let that = this;
   setTimeout(function doAnotherThing(){
      that.numbers.forEach(function log(number){
         console.log(number);
         console.log(that.token);
      });
    }, 100);
  }

Надо заявить let that = this во всех методах использования this во вложенных функциях.

Функция стрелки

Функция стрелки предлагает другой способ устранить эту проблему. Ниже представлен код:

doSomething(){
   setTimeout(() => {
     this.numbers.forEach(number => {
         console.log(number);
         console.log(this.token);
      });
    }, 100);
  }

Функция стрелок не имеет своей this. Это занимает this значение от своего отца. Единственная проблема этой исправления состоит в том, что мы, как правило, теряем название функции. Имя функции важно, поскольку оно улучшает читабельность, выражая намерения функции.

Ниже приведен тот же код с функциями, которые выводят имя переменной:

doSomething(){    
   let log = number => {
     console.log(number);
     console.log(this.token);
   }
    
   let doAnotherThing = () => {
     this.numbers.forEach(log);
   }
    
   setTimeout(doAnotherThing, 100);
}

Метод как обратный вызов

this теряет контекст, когда метод используется как обратный вызов.

Рассмотрим следующий класс:

class Service {
  constructor(){
    this.token = "token"; 
  }
  
  doSomething(){
    console.log(this.token);//undefined
  } 
}
let service = new Service();

Теперь давайте рассмотрим некоторые ситуации, когда метод service.doSomething() используется как обратный вызов.

//callback on DOM event
$("#btn").click(service.doSomething);

//callback for timer
setTimeout(service.doSomething, 0);

//callback for custom function
run(service.doSomething);

function run(fn){
  fn();
}

Во всех предыдущих ситуациях this теряет контекст.

bind()

Мы можем использовать bind() чтобы решить проблему. Просмотрите следующий фрагмент кода:

//callback on DOM event
$("#btn").click(service.doSomething.bind(service));

//callback for timer
setTimeout(service.doSomething.bind(service), 0);

//callback for custom function
run(service.doSomething.bind(service));

Функция стрелки

Другой вариант — создать новую функцию, вызывающую service.doSomething() .

//callback on DOM event
$("#btn").click(() => service.doSomething());

//callback for timer
setTimeout(() => service.doSomething(), 0);

//callback for custom function
run(() => service.doSomething());

Компоненты React

В компонентах React, this теряет контекст, когда методы используются в качестве обратных вызовов для событий UI.

Рассмотрим следующий компонент:

class TodoAddForm extends React.Component {
  constructor(){
      super();
      this.todos = [];
  }
  
  componentWillMount() {
    this.setState({desc: ""});
  }
  
  add(){
    let todo = {desc: this.state.desc}; 
    //Cannot read property 'state' of undefined
    this.todos.push(todo);
  }
  
  handleChange(event) {
     //Cannot read property 'setState' of undefined
     this.setState({desc: event.target.value});
  }
  
  render() {
    return <form>
      <input onChange={this.handleChange} value={this.state.desc} type="text"/>
      <button onClick={this.add} type="button">Save</button>
    </form>;
  }
}

ReactDOM.render(
  <TodoAddForm />,
  document.getElementById('root'));

Способ решения проблемы заключается в создании новых функций в конструкторе с помощью bind(this).

constructor(){
   super();
   this.todos = [];
   this.handleChange = this.handleChange.bind(this);
   this.add = this.add.bind(this);
}

Не используется «this"

Нет this, нет проблем с потерей контекста Объекты можно создавать с помощью заводских функций. Проверьте этот код:

function Service() {  
  let numbers = [1,2,3];
  let token = "token";
  
  function doSomething(){
   setTimeout(function doAnotherThing(){
     numbers.forEach(function log(number){
        console.log(number);
        console.log(token);
      });
    }, 100);
  }
  
  return Object.freeze({
    doSomething
  });
}

В этот раз контекст не теряется, когда метод используется как обратный вызов.


let service = Service();
service.doSomething();

//callback on DOM event
$("#btn").click(service.doSomething);

//callback for timer
setTimeout(service.doSomething, 0);

//callback for custom function
run(service.doSomething);

Вывод

this может потерять контекст в разных ситуациях.

bind()шаблон that/self и функции со стрелками являются инструментами в нашем распоряжении для решения проблем контекста.

Фабричные функции позволяют создавать объекты без использования this совсем.

Откройте для себя функциональный JavaScript был назван одним из лучшие новые книги по функциональному программированию от BookAuthority!

Чтобы узнать больше о применении методов функционального программирования в React, посмотрите Функциональный React.

Учитесь функциональный Reactна основе проекта, с Функциональная архитектура с React и Redux.

Подписывайтесь на Twitter

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *