Dex
Dex

Reputation: 744

How can I bind the 'this' context correctly on a prototype.object?

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!

Upvotes: 0

Views: 210

Answers (3)

Jared Smith
Jared Smith

Reputation: 21926

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.

Upvotes: 0

Kira
Kira

Reputation: 1443

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

Upvotes: 1

deceze
deceze

Reputation: 522025

  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 () { ... };

Upvotes: 2

Related Questions