How to access the correct `this` inside a callback

2020-01-22 07:37发布

I have a constructor function which registers an event handler:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', function () {
        alert(this.data);
    });
}

// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};

// called as
var obj = new MyConstructor('foo', transport);

However, I'm not able to access the data property of the created object inside the callback. It looks like this does not refer to the object that was created but to an other one.

I also tried to use an object method instead of an anonymous function:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', this.alert);
}

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

but it exhibits the same problems.

How can I access the correct object?

10条回答
Juvenile、少年°
2楼-- · 2020-01-22 07:54

Currently there is another approach possible if classes are used in code.

With support of class fields it's possible to make it next way:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

For sure under the hood it's all old good arrow function that bind context but in this form it looks much more clear that explicit binding.

Since it's Stage 3 Proposal you will need babel and appropriate babel plugin to process it as for now(08/2018).

查看更多
做个烂人
3楼-- · 2020-01-22 07:59

The question revolves around how this keyword behaves in javascript. this behaves differently as below,

  1. The value of this is usually determined by a functions execution context.
  2. In the global scope, this refers to the global object (the window object).
  3. If strict mode is enabled for any function then the value of this will be undefined as in strict mode, global object refers to undefined in place of the window object.
  4. The object that is standing before the dot is what the this keyword will be bound to.
  5. We can set the value of this explicitly with call(), bind(), and apply()
  6. When the new keyword is used (a constructor), this is bound to the new object being created.
  7. Arrow Functions don’t bind this — instead, this is bound lexically (i.e. based on the original context)

As most of the answers suggest, we can use Arrow function or bind() Method or Self var. I would quote a point about lambdas (Arrow function) from Google JavaScript Style Guide

Prefer using arrow functions over f.bind(this), and especially over goog.bind(f, this). Avoid writing const self = this. Arrow functions are particularly useful for callbacks, which sometimes pass unexpected additional arguments.

Google clearly recommends to use lambdas rather than bind or const self = this

So the best solution would be to use lambdas as below,

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}

References:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. arrow-functions-vs-bind
查看更多
Luminary・发光体
4楼-- · 2020-01-22 08:00

Another approach, which is the standard way since DOM2 to bind this within the event listener, that let you always remove the listener (among other benefits), is the handleEvent(evt)method from the EventListener interface:

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

Detailed information about using handleEvent can be found here: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

查看更多
Bombasti
5楼-- · 2020-01-22 08:03

The trouble with "context"

The term "context" is sometimes used to refer to the object referenced by this. Its use is inappropriate because it doesn't fit either semantically or technically with ECMAScript's this.

"Context" means the circumstances surrounding something that adds meaning, or some preceding and following information that gives extra meaning. The term "context" is used in ECMAScript to refer to execution context, which is all the parameters, scope and this within the scope of some executing code.

This is shown in ECMA-262 section 10.4.2:

Set the ThisBinding to the same value as the ThisBinding of the calling execution context

which clearly indicates that this is part of an execution context.

An execution context provides the surrounding information that adds meaning to code that is being executed. It includes much more information than just the thisBinding.

So the value of this isn't "context", it's just one part of an execution context. It's essentially a local variable that can be set by the call to any object and in strict mode, to any value at all.

查看更多
乱世女痞
6楼-- · 2020-01-22 08:07

First, you need to have a clear understanding of scope and behaviour of this keyword in the context of scope.

this & scope :


there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope

in short, global scope refers to the window object.Variables declared in a global scope are accessible from anywhere.On the other hand function scope resides inside of a function.variable declared inside a function cannot be accessed from outside world normally.this keyword in global scope refers to the window object.this inside function also refers to the window object.So this will always refer to the window until we find a way to manipulate this to indicate a context of our own choosing.

--------------------------------------------------------------------------------
-                                                                              -
-   Global Scope                                                               -
-   ( globally "this" refers to window object)                                 -     
-                                                                              -
-         function outer_function(callback){                                   -
-                                                                              -
-               // outer function scope                                        -
-               // inside outer function"this" keyword refers to window object -                                                                              -
-              callback() // "this" inside callback also refers window object  -

-         }                                                                    -
-                                                                              -
-         function callback_function(){                                        -
-                                                                              -
-                //  function to be passed as callback                         -
-                                                                              -
-                // here "THIS" refers to window object also                   -
-                                                                              -
-         }                                                                    -
-                                                                              -
-         outer_function(callback_function)                                    -
-         // invoke with callback                                              -
--------------------------------------------------------------------------------

Different ways to manipulate this inside callback functions:

Here I have a constructor function called Person. It has a property called name and four method called sayNameVersion1,sayNameVersion2,sayNameVersion3,sayNameVersion4. All four of them has one specific task.Accept a callback and invoke it.The callback has a specific task which is to log the name property of an instance of Person constructor function.

function Person(name){

    this.name = name

    this.sayNameVersion1 = function(callback){
        callback.bind(this)()
    }
    this.sayNameVersion2 = function(callback){
        callback()
    }

    this.sayNameVersion3 = function(callback){
        callback.call(this)
    }

    this.sayNameVersion4 = function(callback){
        callback.apply(this)
    }

}

function niceCallback(){

    // function to be used as callback

    var parentObject = this

    console.log(parentObject)

}

Now let's create an instance from person constructor and invoke different versions of sayNameVersionX ( X refers to 1,2,3,4 ) method with niceCallback to see how many ways we can manipulate the this inside callback to refer to the person instance.

var p1 = new Person('zami') // create an instance of Person constructor

bind :

What bind do is to create a new function with the this keyword set to the provided value.

sayNameVersion1 and sayNameVersion2 use bind to manipulate this of the callback function.

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}

first one bind this with callback inside the method itself.And for the second one callback is passed with the object bound to it.

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method

p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

call :

The first argument of the call method is used as this inside the function that is invoked with call attached to it.

sayNameVersion3 uses call to manipulate the this to refer to the person object that we created, instead of the window object.

this.sayNameVersion3 = function(callback){
    callback.call(this)
}

and it is called like the following :

p1.sayNameVersion3(niceCallback)

apply :

Similar to call, first argument of apply refers to the object that will be indicated by this keyword.

sayNameVersion4 uses apply to manipulate this to refer to person object

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}

and it is called like the following.Simply the callback is passed,

p1.sayNameVersion4(niceCallback)
查看更多
倾城 Initia
7楼-- · 2020-01-22 08:08

You Should know about "this" Keyword.

As per my view you can implement "this" in three ways (Self/Arrow function/Bind Method)

A function's this keyword behaves a little differently in JavaScript compared to other languages.

It also has some differences between strict mode and non-strict mode.

In most cases, the value of this is determined by how a function is called.

It can't be set by assignment during execution, and it may be different each time the function is called.

ES5 introduced the bind() method to set the value of a function's this regardless of how it's called,

and ES2015 introduced arrow functions which don't provide their own this binding (it retains the this value of the enclosing lexical context).

Method1: Self - Self is being used to maintain a reference to the original this even as the context is changing. It's a technique often used in event handlers (especially in closures).

Reference : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function () {
        alert(self.data);
    });
}

Method2: Arrow function - An arrow function expression is a syntactically compact alternative to a regular function expression,

although without its own bindings to the this, arguments, super, or new.target keywords.

Arrow function expressions are ill suited as methods, and they cannot be used as constructors.

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',()=> {
        alert(this.data);
    });
}

Method3:Bind- The bind() method creates a new function that,

when called, has its this keyword set to the provided value,

with a given sequence of arguments preceding any provided when the new function is called.

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',(function() {
        alert(this.data);
    }).bind(this);
查看更多
登录 后发表回答