55

I want to create a string and pass it by reference such that I can change a single variable and have that propagate to any other object that references it.

Take this example:

function Report(a, b) {
    this.ShowMe = function() { alert(a + " of " + b); }
}

var metric = new String("count");
var a = new Report(metric, "a"); 
var b = new Report(metric, "b"); 
var c = new Report(metric, "c"); 
a.ShowMe();  // outputs:  "count of a";
b.ShowMe();  // outputs:  "count of b";
c.ShowMe();  // outputs:  "count of c";

I want to be able to have this happen:

var metric = new String("count");
var a = new Report(metric, "a"); 
var b = new Report(metric, "b"); 
var c = new Report(metric, "c"); 
a.ShowMe();  // outputs:  "count of a";
metric = new String("avg");
b.ShowMe();  // outputs:  "avg of b";
c.ShowMe();  // outputs:  "avg of c";

Why doesn't this work?

The MDC reference on strings says metric is an object.

I've tried this, which is not what I want, but is very close:

var metric = {toString:function(){ return "count";}};
var a = new Report(metric, "a"); 
var b = new Report(metric, "b"); 
var c = new Report(metric, "c"); 
a.ShowMe();  // outputs:  "count of a";
metric.toString = function(){ return "avg";}; // notice I had to change the function
b.ShowMe();  // outputs:  "avg of b";
c.ShowMe();  // outputs:  "avg of c";

alert(String(metric).charAt(1)); // notice I had to use the String constructor
// I want to be able to call this: 
// metric.charAt(1)

The important points here:

  1. I want to be able to use metric like it's a normal string object
  2. I want each report to reference the same object.
Jeff Meatball Yang
  • 37,839
  • 27
  • 91
  • 125

7 Answers7

72

Strings in Javascript are already passed "by reference" -- calling a procedure with a string does not involve copying the string's contents. There are two issues at hand:

  • Strings are immutable. In contrast to C++ strings, once a JavaScript string has been created it cannot be modified.
  • In JavaScript, variables are not statically assigned slots like in C++. In your code, metric is a label which applies to two entirely separate string variables.

Here's one way to achieve what you want, using closures to implement dynamic scoping of metric:

function Report(a, b) {
    this.ShowMe = function() { alert(a() + " of " + b); }
}

var metric = "count";
var metric_fnc = function() { return metric; }
var a = new Report(metric_fnc, "a"); 
var b = new Report(metric_fnc, "b"); 
a.ShowMe();  // outputs:  "count of a";
metric = "avg";
b.ShowMe();  // outputs:  "avg of b";
John Millikin
  • 197,344
  • 39
  • 212
  • 226
  • 2
    Nicely done. I like this the best because inside Report, I can work with the string with minimal messiness in the source, i.e. a().charAt(1) is much prettier versus String(a).charAt(1) – Jeff Meatball Yang Aug 20 '09 at 20:58
  • 1
    "A closure is a block of code that can be referenced (and passed around) with access to the variables of the enclosing scope." from http://stackoverflow.com/a/5444581/483588 – Ujjwal Singh Dec 22 '12 at 18:09
  • 1
    This doesn't demonstrate the pass by value. What this is doing is simply change the metrics value, of course it will log the new value! var obj = { a: "a" }; var b = obj.a; console.log(obj.a); // a b = "b"; console.log(obj.a); // a – ramaralo Jun 04 '15 at 16:04
47

You can wrap the string in an object and modify the field the string is stored in. This is similar to what you are doing in the last example only without needing to change the functions.

var metric = { str : "count" } 
metric.str = "avg";

Now metric.str will contain "avg"

Matthew Manela
  • 16,572
  • 3
  • 64
  • 66
  • Now this was the answer I had pictured when I had gone to this page. It's more or less the best shorthand for the "Closure?" answer in this list too. http://www.w3schools.com/js/js_objects.asp –  Mar 28 '13 at 17:45
  • Not a closure (per se) but metric is an object that acts like OP desires--only downfall is extra property reference. JavaScript strings are immutable but metric object is mutable, and this metric object provides a level of indirection in such a way that the metric object reference can be shared. – Pete Alvin May 06 '15 at 14:52
10

Closure?

var metric = new function() {
    var _value = "count";

    this.setValue = function(s) { _value = s; };
    this.toString = function() { return _value; };
};

// snip ...
a.ShowMe();

metric.setValue("avg");
b.ShowMe();
c.ShowMe();

or making it a little more generic and performant:

function RefString(s) {
    this.value = s;
}

RefString.prototype.toString = function() { return this.value; }
RefString.prototype.charAt = String.prototype.charAt;

var metric = new RefString("count");

// snip ...

a.ShowMe();

metric.value = "avg";
b.ShowMe();
c.ShowMe();

If you don't close on the desired string variable, then I suppose the only other way would be to modify the ShowMe function, as in @John Millikin's answer or re-architect the codebase.

chakrit
  • 61,017
  • 25
  • 133
  • 162
  • Nice. But I wanted to avoid a setter... and it doesn't scale nicely with calling all of a native String's methods like charAt without manually setting them up. – Jeff Meatball Yang Aug 20 '09 at 21:00
3

If you pass the variable as an object it will work, since objects are passed by reference in Javascript.

http://sirdarckcat.blogspot.com/2007/07/passing-reference-to-javascript.html

function modifyVar(obj,newVal){
obj.value=newVal;
}
var m={value: 1};
alert(x);
modifyVar("x",321);
alert(x);
1

Jsfiddle: https://jsfiddle.net/ncwhmty7/1/

Both string primitives (String Literal) and String objects are immutable. This means that any changes to the content of a string variable while in the function are completely separate from anything that happens outside the function. There are couple of options to overcome this:

1. Returning the modified function value form the function

function concatenateString(stringA, stringB) {
    return stringA + stringB;
}
alert(concatenateString("Hi", " there"));

2. To convert the string variable to a true Object 

function modifyVar(stringA, stringB) {
    var result = stringA + stringB;
    stringA.valueOf = stringA.toSource = stringA.toString = function() {
        return result;
    };
}
var stringA = Object('HI');
modifyVar(stringA, ' there');
alert(stringA);
Razan Paul
  • 13,618
  • 3
  • 69
  • 61
1

If that string is a property of some object you can send the object and stringKey of the property:

    this.modificationFunction = function(object, stringKey){
      object[stringKey] = object[stringKey] + "bla bla bla";
    }

    myObject.myStringProperty = "She said: ";
    this.modificationFunction(myObject, "myStringProperty"); 
    // myObject.myStringProperty is now "She said: bla bla bla";
Marek Manduch
  • 2,303
  • 1
  • 19
  • 22
0

In JavaScript, strings are immutable. You can't change the string itself one the Report instances have a handle to it.

your solution works, but this may be simpler:

function Report(a, b) {
  this.showMe = function() { alert(a.str + " of " + b); }
}

var metric = {};
metric.str = "count";

a.Showme();
metric.str = "avg";
b.ShowMe();
orip
  • 73,323
  • 21
  • 116
  • 148