0

The code example:

function Test() {
    this.name = 'test'
}

Test.prototype.normal = function() {
    console.log('normal', this.name);
};

Test.prototype.special = {
    name: 'special',
    start: function () {
        console.log("special", this.name);
    }
}

test = new Test;

test.normal(); // Response 'normal, test'

test.special.start(); // Response 'special, special'

I'd like the special.start function to log 'special, test'

How can i achieve this? I am using coffeescript so a coffee example would be appreciated but a javascript example works just as well! Thanks in advance!

Dex
  • 625
  • 2
  • 8
  • 21
  • What are you actually trying to accomplish here? And the function's normal `this` is not in any way scoped to the function Test. Nor is `special.start` scoped to a prototype. The `this` value is set dynamically by the JavaScript runtime on every call. – Jared Smith Jul 22 '16 at 15:01
  • Ahh ok thanks for the info – Dex Jul 22 '16 at 15:10
  • `Test.prototype.special.start = Test.prototype.special.start.bind(this);` – Just **no**. You're re-binding the prototype, which affects every single instance. You'll get really confusing results when you add `test2 = new Test` and `test3 = new Test`. – deceze Jul 22 '16 at 15:18
  • Ok thanks I'll change it then to the example you provided. – Dex Jul 22 '16 at 15:20
  • Still no. You're modifying the `.special` object of the prototype, which is also shared between all instances. You need to do it as I show in my answer. – deceze Jul 22 '16 at 15:34
  • Thanks! I've got the code working correctly now for all instances – Dex Jul 22 '16 at 15:54
  • @The person who downvoted. Is the question better now or does it still need editing? Please provide pointers to improve rather than just a simple down-vote click.. – Dex Jul 22 '16 at 16:16
  • @Dex, your question feels like "I wan this output. How should I change my code?". Where is the learning part in this question? I don't think this question will be helpful to anyone other than yourself. – Kira Jul 22 '16 at 16:22
  • @Kira, thanks. How should I rename the question you think? I don't really know the terminology of this problem as was pointed out to me earlier :) – Dex Jul 22 '16 at 16:24
  • I hope you learned something new by now. Think what you didn't know at the time of asking this question and change it accordingly – Kira Jul 22 '16 at 16:25
  • I think editing this question might make it a duplicate to http://stackoverflow.com/questions/3630054/how-do-i-pass-the-this-context-to-a-function – Kira Jul 22 '16 at 16:42
  • I think it is similar due to the context request but not a duplicate since we tried to pre-bind it instead of assinging it each call – Dex Jul 23 '16 at 16:10

3 Answers3

2
  1. The value of this is decided at call time, by how the function is called.
  2. You want to bind to test (the instance), not Test (the constructor function).

If you don't want to make specific call-time adjustments but instead pre-bind the function to the test instance, that can obviously not happen before you have an instance, and it must happen for each instance individually. Hence the only solution to this is:

function Test() {
    this.special = {
        start: (function () { ... }).bind(this)
    };
}

Perhaps you'll want to define the function on the prototype instead of inline; but you'll still need to bind it in the constructor:

function Test() {
    this.special = {
        start: this._start.bind(this)
    };
}

Test.prototype._start = function () { ... };
deceze
  • 471,072
  • 76
  • 664
  • 811
1

I think your question is about this argument in a function. Try using apply or call methods

test = function(){ this.name = 'test'; }

test.prototype.special = { name:'special', start : function(){alert(this.name)} }

var t = new test();

t.special.start.call(t);

t.special.start();

In Javascript, if I call a method like obj.myMethod then this keyword inside myMethod will refer to the variable obj. We can change the value of this keyword inside myMethod using the functions apply, call and bind.

Instead of using call everywhere, we can create a new function with bind

test = function(){ this.name = 'test'; }

test.prototype.special = { name:'special', start : function(){alert(this.name)} }

var t = new test();

//t.start and t.special.start are now different functions as per MDN
t.start = t.special.start.bind(t);

t.start();

t.special.start();

Call

Apply

Kira
  • 1,315
  • 13
  • 44
  • Thx Kira! However now I'd constantly need to use call or apply. Can't I pre-bind this? – Dex Jul 22 '16 at 14:36
  • @Dex Variables in the `prototype` are shared by all instances. If you bound the function to one instance, then it would ruin the `this` binding for any other instances. Perhaps you shouldn't be using the `prototype`. – 4castle Jul 22 '16 at 14:38
  • @ZombieChowder It's preferable to have attribution on links where possible. – 4castle Jul 22 '16 at 14:41
  • there is Function.bind method, instead of calling test.special.start(), you can call like test.start() – Kira Jul 22 '16 at 14:43
  • @Kira The `bind` method is what I was saying would break the function for all other instances. – 4castle Jul 22 '16 at 14:44
  • no, it will not. It will create a new function. We can call it whenever we want or wherever we want – Kira Jul 22 '16 at 14:46
  • @4castle, pls check mdn docs https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind – Kira Jul 22 '16 at 14:47
  • So in the constructor should I add something like: `Test.prototype.special.start.bind(Test);`? – Dex Jul 22 '16 at 14:49
  • Ohh i mean: `Test.prototype.special.start.bind(this);` so it's bound to that specific instance? – Dex Jul 22 '16 at 14:49
  • no, bind will return a new function, something like Test.prototype.special.start = Test.prototype.special.start.bind(this); will do but I would recommend something like Test.prototype.start = Test.prototype.special.start.bind(Test); – Kira Jul 22 '16 at 14:55
  • @Dex no. You can't have it both ways. Either its bound with bind at evaluation time (and would be `undefined` or `window` in your example) or its bound at call time for each instance. Read Deceze's answer. – Jared Smith Jul 22 '16 at 14:57
  • @Kira answering the question the OP asked is good. Answering the *real* question (the one implied by the OPs misunderstanding of how `this` works) is better. Its not actually clear what the OP is trying to do here. – Jared Smith Jul 22 '16 at 14:59
  • @Jared Smith I think i misunderstood .bind() rather than the this scope or am i wrong? – Dex Jul 22 '16 at 15:03
  • @Dex, I explained a little bit about how this works inside a function in answer. Usage of this keyword in JavaScript is not something that I can explain over this answer. You may find the following link useful https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/this – Kira Jul 22 '16 at 15:11
  • unlike apply and call, bind will create a new function instead of calling them directly – Kira Jul 22 '16 at 15:14
  • @Dex see my answer – Jared Smith Jul 22 '16 at 15:23
0

To add to the answers already present based on the comments on Kira's answer, you seem to have a fundamental misunderstanding of how scoping works. Javascript has lexical scoping, meaning scope is defined by the structure of the source code, with one exception: this. this is dynamically scoped, meaning that scope is defined by the caller. In code:

bar = 3
test = (foo) ->
  foo + bar # bar can be seen, because its in an outer scope test can see

So far so good. Now lets define a class:

class Test
  constructor: (@foo) ->
  getFoo: -> @foo

The getFoo part is equivalent to Test.prototype.getFoo = function.... The this (@) is scoped to getFoo. The invoker of getFoo determines the this:

obj1 = new Test 2
obj1.getFoo() # 2

fn = Test.prototype.getFoo;
fn()

fn is now a stand alone function instead of a method of obj1. When we call fn one of two things will happen. If we're in strict mode it will throw an error. Because we're invoking in a global context this in getFoo is undefined. In non-strict it will be the global window and it will look for a foo property that probably isn't there.

We can force the this value via bind, call, or apply.

obj2 =
  foo: 3

Test.prototype.getFoo.call(obj) # 3

Here we've used it to 'borrow' a method of the Test class. So where does bind come in? Lets say that our class has a method we know will be used in a different context, like an event handler:

class Test
  constructor: (@foo) ->
    @clickHandler = Test.prototype.clickHandler.bind this

  clickHandler: (e) ->
    console.log @foo

Because we'll be passing the method to .addEventListener the this context will evaporate, meaning we'll need to bind it to each instance, which we do in the constructor.

Jared Smith
  • 14,977
  • 4
  • 36
  • 57
  • Thanks for your awnser, so what I've writen in the EDIT2 is now valid? – Dex Jul 22 '16 at 15:30
  • @Dex its valid, although the cases where you'd actually *do* that are rare. I used the example of an event handler, but `setTimeout` would also be a legit use case. Basically you only do this when using an object method as a higher-order function (a function argument to another function). – Jared Smith Jul 22 '16 at 15:33