1

In short: Why does a function that is called directly and a function that is passed by reference have a different "this" context in Javascript when using Objects?

In long: I defined an object, and a bunch of methods as prototypes in which use the keyword "this" to access the Objects properties. I instantiate the object and I'm able to call the methods and everything works as expected. The problem is that any of these methods can throw an exception which I want to catch each one individually. To avoid repeating the code I implemented a runCatch function to which I pass the reference to the method to execute and a callback that should be called when an error occurs. Inside the runCatch I'm basically executing the referenced method, and have it wrapped by a try-catch, but like this, the "this" keyword points to the "Window" object instead of the Object itself. So from what I understand about Javascript contexts is that if I initialise the object with new keyword, the "this" contexts inside the prototype functions should be always referencing the Object itself.

Here is a small example of what is happening:

https://jsbin.com/gugohubori/edit?html,js,output

HTML:

<div>Object Value (Direct Call): 
<span id="val1"></span>
<div>Object Value (Passed by Reference): 
<span id="val2"></span>

Javascript:

// The Object
function myobject(){
  this.value = "IT WORKS"
}

myobject.prototype.getValue = function(){
  return this.value;
}

var obj = new myobject(); // Instantiate Object

// Direct Call (this is correct)
document.getElementById('val1').innerText = obj.getValue();

// Call by reference funciton
function callbyref(callback){ return callback(); }

// Call by reference (this is correct)
document.getElementById('val2').innerText = callbyref(obj.getValue);

And the result of the previous code is:

Object Value (Direct Call): IT WORKS

Object Value (Passed by Reference): undefined

Could anybody explain me why the context of "this" differs depending on where you call it? What would be the correct approach for having the callbyref function from the example to have the correct "this" reference pointing to the Object?

EDIT: Where the confusion comes from? So it seems that the main confusion for me was that when you create a regular object "{}" the context of this inside that object is the context where it has been executed. But when you create an object with the new keyword, the context of the methods inside of that object are bound to the object itself, no matter in what context they are called. But for whatever reason that bounded context gets lost when the function is passed as variable and called somewhere else (This is explained pretty well by @Carloluis).


EDIT ABOUT DUPLICATE: Can anybody clarify why this question is being flagged as duplicate? I'm aware that the this variable confusion in Javascript is widely popular and seems to be a delicate problem to ask about, but I've been researching before making the question here. The post that is linked to be the duplicated question doesn't solves my issue and rather explains in a generic way the "this" variable and contexts, but never explains why a object instantiated with new ends up loosing its object context when passed as reference. I think that the answer from @Carloluis is way more clarifying than the link to another unrelated question marked as duplicate.

Robert Koszewski
  • 583
  • 1
  • 8
  • 17
  • `this` by definition is determined by the function’s calling site…as you discovered. To change it use `bind()` — `callbyref(obj.getValue.bind(obj))`. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind – Mark Apr 08 '18 at 18:13
  • JS doesn't pass by reference. It's pass by value. It's because a JS object's value *is its reference*. – Andrew Li Apr 08 '18 at 18:40

2 Answers2

1

The problem is that this keyword value in JavaScript is determined by how the function is called.

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

Read more on MDN.

You can think like this in a function call is pointing to the object previous to the "dot", and if there isn't any . in the call it will point to the windows object (which is the case when you pass the reference of obj.getValue to your callbyref function.

function MyObject(){
  this.value = "IT WORKS"
}

MyObject.prototype.getValue = function(){
  return this.value;
}

const myObject = new MyObject();
console.log(myObject.getValue()); // this -> { value: 'IT WORKS' }

const getValueRef = myObject.getValue;
console.log(getValueRef()); // this -> Windows object

// -- Using the `.bind` function to attach the this context
// In the new function this is permanently bound to the first argument of bind, regardless of how the function is being used.
const getValueRefBound = myObject.getValue.bind(myObject);
console.log(getValueRefBound()); // this -> myObject

Added an example in the previous snippet to show how you can bind the this context to a later function execution despite how it is used.

Function.prototype.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.

You can also check this post Understanding Scope and Context in JavaScript.

Carloluis
  • 4,205
  • 1
  • 20
  • 25
0

Context.

//Creates new instance
var obj = new myobject();
//Calls the method in the instance's context
//When a function is called as a method of an object, this is the object
obj.getValue();

//Grabs the reference to the method
var callback=obj.getValue;
//Calls the referenced method in current context, which is window
callback();

You can try it, add a new method that returns or prints this to the console.

If you need this to always be the object, you should use callback.call(obj,param) or apply(obj,params).

Gabriel
  • 2,170
  • 1
  • 17
  • 20
  • `[...]Calls the referenced method in current context[...]` thats not correct. It will call the function in the **global** (not the **current**) context. – t.niese Apr 08 '18 at 19:03