6

Say I have a class and some static helper methods like this:

function MyClass (myVar) {
    this.myVar = myVar;

    this.replaceMe = function (value) {
        // this will fail
        this = MyClass.staticHelper( value );

        return this;
    }

    this.revealVar = function () {
        alert( this.myVar );
    }
}

MyClass.staticHelper = function (instance, value) {
    return new MyClass( instance.myVar + value );
}

What I want to do is something like this:

var instance = new MyClass( 2 );

instance.revealVar(); // alerts 2
instance.replaceMe( 40 ).revealVar(); // alerts 42

The reason is that my class has a slightly more complicated structure and I don't want to assign all internal variables manually everytime, but rather replace the entire object. Is there a simple way to do so?

Ingo Bürk
  • 19,263
  • 6
  • 66
  • 100
  • 2
    Looks like you just want `return MyClass.staticHelper(this, value );`. You cannot replace `this`, but you can return a new, similar object. – Felix Kling Mar 24 '13 at 12:41
  • This will only return the new object within a call chain, but not actually replace the instance. – Ingo Bürk Mar 24 '13 at 12:41
  • Yes, that's not possible. You can create a wrapper for the whole class and keep an internal reference to the actual instance though. – Felix Kling Mar 24 '13 at 12:42
  • Awe, rats. Would've been nice. I'll just change my design and not overwrite the object but return them to enable call chains. For actual overwriting I can still either reassign the variable or use static functions I have for that. Thanks! – Ingo Bürk Mar 24 '13 at 12:44
  • If you want to "replace" the object by making the current reference a different object, you will need to change all its properties. You can't assign to anything outside of your scope chain. – Bergi Mar 24 '13 at 13:02

4 Answers4

2

instance.replaceMe( 40 ).revealVar(); alerts 42

OK, for that return MyClass.staticHelper(this, value); would suffice. The question is only whether the next call to instance.revealVar() should now alert 2 or 42 - if you want instance to be changed to 42 it gets more complicated:

this = MyClass.staticHelper( value ); // this will fail

…because this is not a common variable, but a keyword and evaluates to the value of the ThisBinding of the current execution context which is set depending on how the function is entered - you cannot assign to it, you can only set it when invoking the function.

I don't want to assign all internal variables manually everytime, but rather replace the entire object.

Unfortunately you have to do so, without changing the properties of instance object (and the closure-hidden variables) you won't change the instance and revealVar() will stay 2.

Is there a simple way to do so?

Yes, it can be done programmatically. The simplest method would be to call the constructor (again) on the current instance, like it happens when invoked with the new keyword:

MyClass.call( instance, instance.myVar + value );

Yet you can't use this like the static function which creates a completely new instance. Either you put it in a static method and call that from replaceMe with this, or you just put it directly in replaceMe.

If you need a static method that at first returns a completely new instance, you could use that as well by copying the new properties on the old instance:

….replaceMe = function(val) {
    var newInst = MyClass.staticHelper(this, val); // new MyClass(this.myVar+val);
    for (var prop in newInst)
        if (newInst.hasOwnProperty(prop))
            this[prop] = newInst[prop];
    return this;
};

That means overwriting the old attributes, and also the old closures can be garbage-collected now as nothing refers to them any more.

Btw, I'd recommend to put your methods on the prototype instead of assigning them in the constructor.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

How about just returning the new instance:

function MyClass(myVar) {
    // ...

    this.replaceMe = function (value) {
        return MyClass.staticHelper(this, value);
    }

    // ...
}

MyClass.staticHelper = function (instance, value) {
    return new MyClass( instance.myVar += value );
}
David G
  • 94,763
  • 41
  • 167
  • 253
  • As mentioned on the comments of the question, this won't actually replace the instance, i.e. outside of a call chain the object will be untouched. – Ingo Bürk Mar 24 '13 at 12:45
  • @IngoBürk I also added `+=` instead of `=` so that will mutate the object property. – David G Mar 24 '13 at 12:45
  • Oh, I didn't see that. But that defeats the purpose of my question: My class needs an update on several variables upon a change. I was looking for a way not to have to do that manually. – Ingo Bürk Mar 24 '13 at 12:46
  • @IngoBürk Well you can't change `this` as it is a constant object. I think you could use the module pattern for this. I'll update shortly. – David G Mar 24 '13 at 12:49
  • Yeah, I understood that now. I just thought maybe there's a way, after all Javascript is a language with a lot of "features". :) – Ingo Bürk Mar 24 '13 at 12:50
0

There are two reasons why this is not going to work in Javascript.

First, despite that it looks like a variable, this is actually a function call* and therefore cannot be assigned to. this=foo is the same as bar()=baz. So it's not possible to have code like this:

a = 5
a.change(10)
alert(a == 10) // nope

Second, even if this=z were possible, that approach would fail anyways, because Javascript passes by value, therefore it's not possible to have a function that changes the value of its argument:

a = 5
change(a)
alert(a == 10) // nope

* "is" means "fully identical in every way"

georg
  • 211,518
  • 52
  • 313
  • 390
  • 3
    The specification does not say that it is a function call. [It only mentionds `ThisBinding`](http://es5.github.com/#x10.4.3) which is described as *"The value associated with the `this` keyword within ECMAScript code associated with this execution context."* (http://es5.github.com/#x10.3, http://es5.github.com/#x11.1.1). – Felix Kling Mar 24 '13 at 12:56
  • The `this` pointer inside a function is a read-only value, not a function call. That's the reason assignments to `this` do not work. – Aadit M Shah Mar 24 '13 at 14:17
  • 1
    @AaditMShah: "read-only value" is somehow an oxymoron, because all values are read-only (`5=x` won't work either). If you mean "read-only _variable_" this is not correct either, since variables retain their values, while `this` does not. – georg Mar 24 '13 at 16:15
  • @thg435 - Oops, I did indeed mean read-only variable. That being said what does memory retention have anything to do with a variable being read-only or read-write? The `this` pointer does retain its value and its value is a reference (an address) to an object in memory. – Aadit M Shah Mar 24 '13 at 16:30
  • @AaditMShah: With a regular variable, once we assign something to it, this value remains there until we reassign it. The value of `this` changes "by itself" as we enter a new function scope (there's another "variable" with the same property though). That's why tricks like `var me=this` are needed when creating closures with `this`. – georg Mar 24 '13 at 16:43
  • @thg435 - The value of `this` does not change when you enter the scope of a new function. If that were the case then the language would be severely borked. Each function has it's own `this` pointer which [shadows](http://en.wikipedia.org/wiki/Variable_shadowing "Variable shadowing - Wikipedia, the free encyclopedia") the `this` pointer in its parent functions. The `this` pointer of the parent remains the same (just like any other variable), but is completely inaccessible as it's being shadowed and `this` can't be `delete`d to unshadow it. That's the reason we assign `this` to another variable. – Aadit M Shah Mar 25 '13 at 03:21
0

I wanted to do something very similar a while back. Unfortunately there's no way to assign a value to this - the this pointer is a read only variable. However the next best thing is to use a getter and setter object to change the variable holding your instance itself.

Note that this only updates a single reference to the instance. You can read more about it here: Is there a better way to simulate pointers in JavaScript?

So this is how it works:

function MyClass(pointer, myVar) {
    this.myVar = myVar;

    this.replaceMe = function (value) {
        pointer.value = MyClass.staticHelper(this, pointer, value);
        return pointer.value;
    };

    this.revealVar = function () {
        alert(this.myVar);
    };
}

MyClass.staticHelper = function (instance, pointer, value) {
    return new MyClass(pointer, instance.myVar + value);
};

This is how to create the pointer and use it:

var instance = new MyClass({
    get value() { return instance; },
    set value(newValue) { instance = newValue; }
}, 2);

instance.revealVar();               // alerts 2
instance.replaceMe(40).revealVar(); // alerts 42

It's not the most elegant solution but it gets the job done. You can see this code in action: http://jsfiddle.net/fpxXL/1/

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299