0

Is there any way to make sure that this will always be the same thing when accessed in an object?

I have the following code:

var stack = [];
var callStack = function ()
{
 for (var i in stack) {
  // does not work
  stack[i][0].apply(null, stack[i][1]);
  // does not work either
  stack[i][0].apply(stack[i][0], stack[i][1]);
 }
};
var testThis = {
 bar : "something",
 show : function()
 {
  console.log(this.bar, arguments);
 }
}

testThis.show('standardCall');
stack.push([testThis.show, ['someVar']]);
stack.push([testThis.show, ['otherVar', 'yetAnotherVar']]);
callStack();

What I want to achieve is this: I want to be able to prepare a stack of functions/methods to be called later (this is just a stripped down example, in reality, the calls will be spread throughout the entire application).

Within the objects, I want to have access to methods and properties of the object just as if they were called "normally" (as shown in the example by testThis.show('standardCall'), which works just as expected), i.e. I need this to always be the same within the method no matter how the method is called. In this example, I want this.bar to always display "something". Is there any way to ensure this behavior? The call/apply methods do not seem to work.

Note: I'm looking for a universal solution, so, obviously, I can't solve it by referencing "bar" some other way (like testThis.bar), removing it from the object's context etc.)

Malis
  • 220
  • 2
  • 15
  • 4
    [`Function.prototype.bind()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/function/bind) – Andreas Jan 11 '16 at 21:01
  • 2
    Possible duplicate of [How does the "this" keyword in Javascript act within an object literal?](http://stackoverflow.com/questions/13441307/how-does-the-this-keyword-in-javascript-act-within-an-object-literal) – Tamas Hegedus Jan 11 '16 at 21:06
  • @hege_hegedus That question is misleading, it doesn't really matter if `this` is within an object literal or not. See http://stackoverflow.com/a/13441340/227299 – Ruan Mendes Jan 12 '16 at 15:27

2 Answers2

2

this is determined by how a function is called. Therefore, when you store your functions, you also have to specify what to use as this so that you can pass it as the first parameter to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

var stack = [];
var callStack = function (){
  for (var i=0; i < stack.length; i++) {
    // Setting what this will be
    //                     v
    stack[i][0].apply(stack[i][1], stack[i][2]);
  }
};
var testThis = {
  bar : "something",
  show : function() {
    console.log("testThis.show "  + this.bar, arguments);
  }
};

var testThat = {
    foo: 1,
    doIt: function(some) {
       console.log('testThat.doIt Foo is '+ this.foo + ' and ' + some);
    } 
}

stack.push([testThis.show,  testThis, ['someVar']]);
stack.push([testThis.show, testThis, ['otherVar', 'yetAnotherVar']]);
stack.push([testThat.doIt,  testThat, ['anything']]);
stack.push([testThat.doIt, testThat, ['something']]);

callStack();

Alternatively, you could bind the function to specify what this would be, see the answer by Alex

Community
  • 1
  • 1
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • Thanks, this is actually how I'm dealing with it right now. But as it the calls can be more complicated (testThis.go.very.deep.myMethod...), I'm afraid that this can easily create room for typo error. I thought that there might be a way to force it to work this way automatically. In other words - to make sure that whatever is called via the callStack uses "this" automatically as if called directly. – Malis Jan 11 '16 at 21:34
  • 2
    Unless you use a [fat arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), it is always the caller that determines what `this` is, there's no way around that. Even if you use a fat arrow function, `this` will still be dependent on how the container function is called. There are three ways this is handled: bind the function, attach `var self = this` in the outer function and use `self` instead of `this`. For ease of use, some callback mechanisms let you specify `this` for the callback. – Ruan Mendes Jan 11 '16 at 21:42
  • Hmm, not really what I _wanted_ to hear but kinda what I expected. It's somewhat weird that a method doesn't have a guaranteed access to its own object but I assume this is not a place for philosophical debates on how logic of some programming languages makes no sense at times, so I'm gonna swallow the curse and accept your answer :) – Malis Jan 11 '16 at 21:47
  • 2
    @Malis the problem is that objects in JavaScript don't "own" anything - they just have properties, and those properties have values. Functions have no special relationship to any particular object unless they *explicitly* do (like, they make explicit references or are created with `.bind()`) or unless they're fat-arrow functions. – Pointy Jan 11 '16 at 21:49
  • @Malis, in the case of `var a = {b: {c: {d: function() {console.log(this)}}}}` What would `this` point to? If you call `a.b.c.d()`, it will be the `a.b.c` object, but some may want it to be `a`. As Pointy pointed out, the function is not owned by the object. You can have the same function being referenced from many different objects. – Ruan Mendes Jan 11 '16 at 21:49
  • I'm aware of that. Actually, what I was "complaining" about was not that it is a or a.b.c but that it can be a different thing at different times. But you were absolutely right that I absolutely misunderstood the concept of `this` from this point of view and will have to get over it. That's it. – Malis Jan 11 '16 at 21:55
1

You just need to bind your scope - here's a couple of ways:

stack.push([testThis.show.bind(testThis), ['someVar']]);
stack.push([testThis.show.bind(testThis), ['otherVar', 'yetAnotherVar']]);

Or, if you 100% ALWAYS want to guarantee this function executes in the context of testThis, you can do this:

testThis.show = testThis.show.bind(testThis);

then you can continue as normal:

stack.push([testThis.show, ['someVar']]);
stack.push([testThis.show, ['otherVar', 'yetAnotherVar']]);

and anytime the function is called - even if its later called with .bind(..a different object...) - this will be the testThis object.

Alex McMillan
  • 17,096
  • 12
  • 55
  • 88
  • Thank you for your time to answer this question, I'm just afraid this doesn't solve the "general" problem - I'd like the `this` to be **automatically** attached the "right" way when called via the `callStack` method. I could, of course, bind the scope manually for each call but I already do that as proposed by @Juan in his answer. Now I'm trying to find a general way that would get me rid of the duplicity (and thus room for a potential error). – Malis Jan 11 '16 at 21:39
  • 1
    @Malis You are misunderstanding JavaScript. There is no magic/correct behavior, in JavaScript, `this` is determined by how a function is called, except for fat arrow functions, but they wouldn't help with the specific example you have. – Ruan Mendes Jan 11 '16 at 21:44
  • 1
    @Malis This issue is commonly misunderstood by developers who often work with class-based, strongly-typed languages (C#, Java etc). Javascript is very different. There is no "right" way to scope a function call - you decide the scope by the way you execute the function. If you google for terms like "javascript this" and "javascript function scope" you will see many, **many** different blogs/tutorials explaining how scope and `this` work. Good luck! – Alex McMillan Jan 11 '16 at 21:53