8

I've always been taught the correct way to simulate a class in JavaScript is by adding methods to the prototype outside the function that will be your class, like this:

function myClass()
{
    this.myProp = "foo";
}

myClass.prototype.myMethod = function()
{
    console.log(this);
}

myObj = new myClass();
myObj.myMethod();

I've been running into the issue that the this in my methods resolves to the global Window object, as explained best on quirksmode.

I've tried doing the var that = this; trick Koch mentions, but since my methods are outside my class, my that variable is no longer in scope. Perhaps I'm just not understanding it completely.

Is there a way I can create a class in JavaScript where methods are not recreated each implementation and this will always point to the object?

EDIT:

The simplified code above works but I've had many times where I declare a "class" exactly like above and when I call myObj.myMethod(), it comes back as a Window object. I've read over every explanation of this that I could find, such as the one I linked to and still don't understand why this problem sometimes happens. Any idea of a situation where the code could be written like above and this would refer to Window?

Here's the implementation I'm currently having problems with, but when I simplify it down like above into a few lines, I no longer have the problem:

HTML File:

<script type="text/javascript" src="./scripts/class.Database.js"></script>
<script type="text/javascript" src="./scripts/class.ServerFunctionWrapper.js"></script>
<script type="text/javascript" src="./scripts/class.DataUnifier.js"></script>
<script type="text/javascript" src="./scripts/main.js"></script>

class.DataUnifier.js:

function DataUnifier()
{
    this._local = new Database();
    this._server = new ServerFunctionWrapper();
    this.autoUpdateIntervalObj = null;
}

DataUnifier.prototype.getUpdates = function()
{
    this._server.getUpdates(updateCommands)
    {
        console.log(updateCommands);
        if (updateCommands)
        {
            executeUpdates(updateCommands);
        }
    }
}
//interval is in seconds
DataUnifier.prototype.startAutoUpdating = function(interval)
{
    this.stopAutoUpdating();
    this.autoUpdateIntervalObj = setInterval(this.getUpdates,interval * 1000);
}
DataUnifier.prototype.stopAutoUpdating = function()
{
    if (this.autoUpdateIntervalObj !== null)
    {
        clearInterval(this.autoUpdateIntervalObj);
        this.autoUpdateIntervalObj = null;
    }
}

main.js

var dataObj = new DataUnifier();

$(document).ready(function ev_document_ready() {
    dataObj.startAutoUpdating(5);
}

I've cut out some code that shouldn't matter but maybe it does. When the page loads and dataObj.startAutoUpdating(5) is called, it breaks at the this.stopAutoUpdating(); line because this refers to the Window object. As far as I can see (and according to the link provided), this should refer to the DataUnifier object. I have read many sources on the this keyword and don't understand why I keep running into this problem. I do not use inline event registration. Is there any reason code formatted like this would have this problem?

EDIT 2: For those with similar issues, see "The this problem" half way down the page in this Mozilla docs page: http://developer.mozilla.org/en-US/docs/Web/API/Window.setInterval

dallin
  • 7,138
  • 1
  • 29
  • 36
  • There is nothing wrong with your posted code. Run it, it'll work fine. – Mark Kahn Apr 24 '14 at 00:59
  • @PointedEars I am well aware there are no classes in JavaScript. Thanks. I meant "simulating classes." I'll post more code shortly and we'll see if we can tell what's going on. – dallin Apr 24 '14 at 01:02
  • @PointedEars Calling this a duplicate of that question is quite a stretch. Rather, a better understanding of `this` would have helped me, which the other question provides. A link to the other question might have been better. I tried to show I had done research by providing a very similar link in my question to yours, but still didn't quite grasp why my implementation wasn't working (which I found out wasn't the problem). My question was is there a way to simulate a class where I wouldn't run into issues with `this`. I couldn't find a question on SO like that. – dallin Apr 24 '14 at 17:42
  • @PointedEars I had thoroughly read up on it and thought I understood but was just short of getting it. I was also plenty clear in my original question I understood there was no such thing as JavaScript classes. I purposely used wording like "simulate a class" because I *knew* someone would post that. Just being honest, not confrontational - I was left feeling like you did a drive by glance, didn't fully read, and panned me: http://goo.gl/gpRgVt. I'm certain I can improve, but I tried very hard with this question to follow the guidelines and provide a good question for SO. – dallin Apr 24 '14 at 17:44
  • 2
    @PointedEars Anyway, I do thank you for trying to help me. Frequent contributors keep SO alive and pumping, so thank you. – dallin Apr 24 '14 at 17:47

4 Answers4

2

The Answer

The problem is not with this.stopAutoUpdating();, it is with:

setInterval(this.getUpdates, interval * 1000);

When you pass a function like this to setInterval it will be called from the event loop, with no knowledge of the this you have here. Note that this has nothing to do with how a function is defined, and everything to do with how it is called. You can get around it by passing in an anonymous function:

var self = this;
setInterval(function(){ self.getUpdates(); }, interval * 1000);

In any modern engine you can use the much nicer bind:

setInterval(this.getUpdates.bind(this), interval * 1000);

You can also use bind in older engines if you shim it first.


Understanding the Problem

I recommend that you read about call and apply for a better understanding.

Note that when you call a function normally, without using bind, call, or apply, then the this will just be set to whichever object context the function was called from (that is, whatever comes before the .).

Hopefully this helps you understand what I said about this not being about how the function is defined, rather how it is called. Here is an example, where you might not expect this to work, but it does:

// This function is not defined as part of any object
function some_func() {
  return this.foo;
}
some_func(); // undefined (window.foo or an error in strict mode)

var obj = {foo: 'bar'};

// But when called from `obj`'s context `this` will refer to obj:

some_func.call(obj); // 'bar'

obj.getX = some_func;
obj.getX(); // 'bar'

An example where you might expect it to work, but it doesn't, along with a couple solutions to make it work again:

function FooBar() {
  this.foo = 'bar';
}
FooBar.prototype.getFoo = function() {
  return this.foo;
}

var fb = new FooBar;
fb.getFoo(); // 'bar'

var getFoo = fb.getFoo;

// getFoo is the correct function, but we're calling it without context
// this is what happened when you passed this.getUpdates to setInterval
getFoo(); // undefined (window.foo or an error in strict mode)

getFoo.call(fb); // bar'

getFoo = getFoo.bind(fb);
getFoo(); // 'bar'
Paul
  • 130,653
  • 24
  • 259
  • 248
  • Thank you! Sometimes I wish I could mark two answers as accepted. I understood the whole owner thing, I was confused over what context setInterval would call my function from. – dallin Apr 24 '14 at 19:03
  • shouldn't `fb.getFoo(); // 'foo'` in your last example be `fb.getFoo(); // 'bar'` since it outputs 'bar'.. – jack blank Aug 20 '15 at 06:31
2

My favorite way of defining classes is as follows:

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

Using the defclass function you can define MyClass as follows:

var MyClass = defclass({
    constructor: function () {
        this.myProp = "foo";
    },
    myMethod: function () {
        console.log(this.myProp);
    }
});

BTW your actual problem is not with classes. It's the way you're calling this.getUpdates from setTimeout:

this.autoUpdateIntervalObj = setInterval(this.getUpdates, interval * 1000);

Instead it should be:

this.autoUpdateIntervalObj = setInterval(function (self) {
    return self.getUpdates();
}, 1000 * interval, this);

Hence your DataUnifier class can be written as:

var DataUnifier = defclass({
    constructor: function () {
        this._local = new Database;
        this._server = new ServerFunctionWrapper;
        this.autoUpdateIntervalObj = null;
    },
    getUpdates: function () {
        this._server.getUpdates(function (updateCommands) {
            console.log(updateCommands);
            if (updateCommands) executeUpdates(updateCommands);
        });
    },
    startAutoUpdating: function (interval) {
        this.stopAutoUpdating();
        this.autoUpdateIntervalObj = setInterval(function (self) {
            return self.getUpdates();
        }, 1000 * interval, this);
    },
    stopAutoUpdating: function () {
        if (this.autoUpdateIntervalObj !== null) {
            clearInterval(this.autoUpdateIntervalObj);
            this.autoUpdateIntervalObj = null;
        }
    }
});

Succinct isn't it? If you need inheritance then take a look at augment.

Edit: As pointed out in the comments passing additional parameters to setTimeout or setInterval doesn't work in Internet Explorer versions lesser than 9. The following shim can be used to fix this problem:

<!--[if lt IE 9]>
    <script>
        (function (f) {
            window.setTimeout = f(window.setTimeout);
            window.setInterval = f(window.setInterval);
        })(function (f) {
            return function (c, t) {
                var a = [].slice.call(arguments, 2);

                return f(function () {
                    c.apply(this, a);
                }, t);
            };
        });
    </script>
<![endif]-->

Since the code is only executed conditionally on Internet Explorer versions lesser than 9 it is completely unobtrusive. Just include it before all your other scripts and your code will work on every browser.

Aadit M Shah
  • 67,342
  • 26
  • 146
  • 271
  • Good answer, but note that passing `this` as an additional argument to `setInterval` like that doesn't work in Internet Explorer. – Paul Apr 24 '14 at 02:43
  • You can always use a shim for `setInterval` and `setTimeout` in Internet Explorer. – Aadit M Shah Apr 24 '14 at 04:28
  • @YTowOnt9 I edited my answer. Does that help? – Aadit M Shah Apr 24 '14 at 04:38
  • Yes, +2 if I could upvote again :) – Paul Apr 24 '14 at 04:42
  • Thanks Aadit. I fixed it with `var self = this; setInterval(function(){ self.getUpdates(); }, interval * 1000);`. I understood the owner concept with `this`, just not how it works with passing. For others with this same problem, I found a really good overview of `this` used in setInterval here: https://developer.mozilla.org/en-US/docs/Web/API/Window.setInterval. Go half way down the page to "The "`this`" problem". – dallin Apr 24 '14 at 19:41
0

There is no "correct" way to simulate classes. There are different patterns you could use. You could stick with the one you are using right now. The code you posted works correctly. Or you could switch to another pattern.

For example Douglas Crockford advocates doing something like this

function myClass() {
    var that = {},
        myMethod = function() {
            console.log(that);
        };
    that.myMethod = myMethod;
    return that;
}

Watch his talk on youtube. http://youtu.be/6eOhyEKUJko?t=48m25s

SpiderPig
  • 7,711
  • 1
  • 17
  • 36
  • 2
    Douglas Crockford has had a few good ideas; this is one of the worse ones. Instead of `that`, you could have simply written `this`: `var that = {myMethod: function () { console.log(this); }};`. All the `that` reference does is to *increase* the length of the effective scope chain, implicitly adding a *closure*, thus [*decreasing* the efficiency of the code](https://developers.google.com/speed/articles/optimizing-javascript), and to *hinder* the reuse of the method because it is tied to its owner. Just don’t. – PointedEars Apr 24 '14 at 01:01
  • 1
    There are widely varying opinions about this. Sure it can reduce performance a little but that will not be noticeable in most cases. The purpose of this design is to get rid of the broken behaviour of "this" which is responsible for tons of bugs in js code. And reusing methods in the way you implied, i.e by rebinding them to another "this" is a very questionable programming style anyway. – SpiderPig Apr 24 '14 at 01:11
  • 2
    There is absolutely nothing broken about `this`, only with beginners’ perception of it. – PointedEars Apr 24 '14 at 01:14
  • 1
    @PointedEars I think that might be a matter of opinion, as it works differently from any other language I know of and is a frustration for many even after they understand it. I guess broken might be the wrong word to use, as it does work as intended. – dallin Apr 24 '14 at 01:41
-2

I use the style bellow:

function MyClass(){
    var privateFunction = function(){

    }

    this.publicFunction = function(){
        privateFunction();
    }

}

For me this is much more intuitive than using the prototype way, but you'll reach a similar result combining apply() method.

Also, you have another good patterns, Literal Object Notation, Revealing Module Pattern

But, if you want to change the reference to this associate of a function use the Function.prototype.apply(), you can see at this sample a way to change the global this to your object.

Community
  • 1
  • 1
Claudio Santos
  • 1,307
  • 12
  • 22
  • 2
    Scope has nothing to do with `this`; so `Function.prototype.apply()` has nothing to do with scope, it has to do with the `this` value in the called function. That is a common misconception in beginners. – PointedEars Apr 24 '14 at 00:50
  • 1
    Your (now corrected) style has the disadvantage that with each constructor call you are creating (here: two) new `Function` instances. IOW, for creating one hundred `myClass` instances, you are creating *three hundred* objects and reserving heap memory for them. This pattern makes sense if you really need instance-private parts; in all other cases it is overkill and constitutes massive overhead. BTW, by convention the identifier of a constructor starts *uppercase*. – PointedEars Apr 24 '14 at 01:08
  • I don't know this too, but I'm using only for unique instances. I usually use a functional programming style to iterate on multiple objects. – Claudio Santos Apr 24 '14 at 01:14
  • You do not need a constructor for a singleton in these languages. – PointedEars Apr 24 '14 at 01:16
  • Sure... but as I sad, I don't use this way of declaration, tks a lot. – Claudio Santos Apr 24 '14 at 01:20
  • I used the word scope because, when you change the `this` object you get access to properties referenced with the `this` object. – Claudio Santos Apr 24 '14 at 01:22
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/51311/discussion-between-claudio-santos-and-pointedears) – Claudio Santos Apr 24 '14 at 01:40