2

Let's assume we have the following code:

var MyClass = (function(){
    var _this;

    function MyClass(inputVal){
        _this = this;
        this.value = inputVal;
    }

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

    MyClass.prototype.getValue2 = function(){
        return _this.value;
    }

    return MyClass;
})();

Let's make two instances of the class:

var instance1 = new MyClass(10);
var instance2 = new MyClass(20);

Now if we console.log() the values we see that:

instance1.getValue();   // 10
instance1.getValue2();  // 20

var MyClass = (function(){
    var _this;

    function MyClass(inputVal){
        _this = this;
        this.value = inputVal;
    }

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

    MyClass.prototype.getValue2 = function(){
        return _this.value;
    }

    return MyClass;
})();


var instance1 = new MyClass(10);
var instance2 = new MyClass(20);


console.log(instance1.getValue());
console.log(instance1.getValue2());

Why is that happening? It looks obviously that the _this variable gets the latest created instance properties. How to fix that? I need to keep a copy of this. Thanks!

Edit:

Here's the real situation

var HoverEffects = (function(){
    var _this;

    function HoverEffects($nav){
        _this = this;
        this._$activeNav = $nav.siblings('.active_nav');
        this._$hoverableLis = $nav.find('>li');
        this._$activeLi = $nav.find('>li.active');

        if(!$nav.length || !this._$hoverableLis.length || !this._$activeNav.length || !this._$activeLi.length) return;

        if(this._$activeNav.hasClass('bottom')){
            this._$activeNav.align = 'bottom';
            this._$activeLi.cssDefault = {
                left: this._$activeLi.position().left,
                width: this._$activeLi.width()
            };
        }
        else if(this._$activeNav.hasClass('left')){
            this._$activeNav.align = 'left';
            this._$activeLi.cssDefault = {
                top: this._$activeLi.position().top,
                height: this._$activeLi.height()
            };
        }
        else{
            return;
        }

        this._$hoverableLis.hover(
            function(){

                // How to set the correct this inside this function?
                if(this._$activeNav.align === 'bottom'){
                    this._$activeNav.css({
                        left: $(this).position().left,
                        width: $(this).width()
                    });
                }
                else if(this._$activeNav.align === 'left'){
                    this._$activeNav.css({
                        top: $(this).position().top,
                        height: $(this).height()
                    });
                }

            },

            function(){
                // Same here, wrong this
                this._$activeNav.css(this._$activeLi.cssDefault);
            }
        );
    }

    return HoverEffects;
})();

var sideNavHoverMagic = new HoverEffects($('#side-navigation'));
var primaryNavHoverMagic = new HoverEffects($('#primary-navigation'));
Ezio_
  • 593
  • 3
  • 9
  • 23
  • 1
    "I need to keep a copy of `this`" what for exactly? What is it that you're attempting to accomplish. Prototype methods already have access to `this`. Are you attempting to pass `getValue` as a callback to another method? – zzzzBov Nov 09 '16 at 15:30
  • @zzzzBov Because I need to call a private method from a nested function. For instance - the jquery's $('#element').hover(function(){ privateFunc.call(this); },function(){ privateFunc.call(this); }); won't work, as it is not passing the correct context, and you need _this here. – Ezio_ Nov 09 '16 at 15:38
  • 1
    You're passing `this` incorrectly there because `this` isn't referring to the object instance you want. You'll need to show more code for me to point out your specific issue. Storing the instance *outside* the constructor is the wrong way to go about solving the problem you're having. – zzzzBov Nov 09 '16 at 15:41
  • You have to keep a reference to `this` *where* you make the call to the jQuery function. Currently there is only a single `_this` that is shared by *all* instances. This might help: [How to access the correct `this` / context inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-context-inside-a-callback) – Felix Kling Nov 09 '16 at 15:43
  • What are you trying to achieve? It just don't make sense. Please elaborate, what is your motivation. Why are u trying to magically bind `this` of two independent instances of same class? Why not just `http://jsbin.com/bokigox/edit?html,js,console`? – mauron85 Nov 09 '16 at 15:53
  • Please check the updated question – Ezio_ Nov 09 '16 at 16:02

1 Answers1

5

Why is that happening?

Every time you call new MyClass, _this = this gets run. The second time overrides the first time.

So _this refers to new MyClass(20), which means that when you call getValue2 from any MyClass instance, 20 will be returned because all MyClass instances are referring to the same _this value.


Based on commentary on the Question:

If you're attempting to pass a function bound to the appropriate context there are a variety of ways to make sure that this refers to the right object. Before continuing, please read "How does the 'this' keyword work?", because there's no reason for me to repeat all of it here.

If you're binding event callbacks such as in a constructor:

function Example(something) {
    something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
    this.doStuff();
    this.doMoreStuff();
};

The callback will have the wrong this value because it's not being called as this.callback, it's just being called as:

fn = this.callback;
fn(); //no reference to this

You can get around this in a number of ways.

Function.prototype.bind

You can bind the callback for every instance on their respective instance. This is very concise:

function Example(something) {
    //generate a new callback function for each instance that will
    //always use its respective instance
    this.callback = this.callback.bind(this);
    something.addEventListener(..event.., this.callback, false);
}
Example.prototype.callback = function () {
    this.doStuff();
    this.doMoreStuff();
};

that = this

You can create the callback (closure) within the constructor and reference a variable inside the constructor.

function Example(something) {
    //every Example object has its own internal "that" object
    var that = this;
    this.callback = function () {
        //this function closes over "that"
        //every instance will have its own function rather than
        //a shared prototype function.
        that.doStuff();
        that.doMoreStuff();
    }

    something.addEventListener(..event.., this.callback, false);
}

() => {} (Fat Arrow Syntax)

If you're using ES2015 you can use "fat arrow" syntax for creating lambdas that don't create a new context:

function Example(something) {
    this.callback = () => {
        //the callback function doesn't create a new "this" context
        //so it referes to the "this" value from "Example"
        //every instance will have its own function rather than
        //a shared prototype function.
        that.doStuff();
        that.doMoreStuff();
    }

    something.addEventListener(..event.., this.callback, false);
}
Community
  • 1
  • 1
zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • `The second time overrides the first time.` I don't think `var instance2 = new MyClass(20);` would override `var instance1 = new MyClass(10);`. They are 2 difference instances. – Tân Nov 09 '16 at 15:33
  • @TânNguyễn: `MyClass` gets called twice, the second call assigns a new value to `_this` (the value is *overwritten*). – Felix Kling Nov 09 '16 at 15:36
  • @TânNguyễn, I never said that `instance2` overrides `instance1`. I said that `_this = this` will be called every time the constructor is executed, which is where the issue comes from. – zzzzBov Nov 09 '16 at 15:36
  • @zzzzBov How to keep a correct _this value? Is there anything wrong with the pattern I used? – Ezio_ Nov 09 '16 at 15:41