Лучший способ избежать потери контекста this – не использовать this. Однако, это не всегда возможно. Например, мы работаем с чужим кодом или библиотекой, которые используют this.
Литерал объекта, функция-конструктор, конструктор объектов класса в системе прототипов. Псевдопараметр this используется в системе прототипирования для того, чтобы дать доступ к свойствам объекта.
Давайте рассмотрим несколько случаев.
Вложенные функции (Nested Functions)
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();
У метода 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.
function doAnotherThing(){ /*…*/}.bind(this) создает версию функции doAnotherThing(), которая берет значение this из doSomething().
that/self
Другой вариант – объявить и использовать новую переменную 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 во вложенных функциях.
Стрелочные функции (Arrow function)
Стрелочная функция даёт нам еще один способ решения этой проблемы.
doSomething(){ setTimeout(() => { this.numbers.forEach(number => { console.log(number); console.log(this.token); }); }, 100); }
Стрелочные функции не создают собственный контекст для this, а используют значение this окружающего контекста. В примере выше она использует значение this родительской функции.
Недостаток этого способа в том, что мы не можем задать имя стрелочной функции. Имя функции играет важную роль, так как повышает читабельность кода и описывает её назначение.
Ниже представлен тот же код с функцией, выраженной через имя переменной:
doSomething(){ let log = number => { console.log(number); console.log(this.token); } let doAnotherThing = () => { this.numbers.forEach(log); } setTimeout(doAnotherThing, 100); }
Функции обратного вызова (Method as callback)
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 Components)
В компонентах this теряет ссылку на контекст, когда методы используются в качестве коллбэков для событий.
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 — нет проблем с потерей контекста. Объекты могут создаваться с помощью фабричных функций (factory functions). Посмотрите на этот пример:
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.
ссылка на оригинал статьи https://habr.com/post/421959/
Добавить комментарий