Here’s a question I was pondering a couple of days ago: Can you make JavaScript’s String object mutable, as in modifiable without having to re-assign the string.
Why would this be useful? Sometimes you might share a string between objects, and you’d want changing it in one place get reflected in elsewhere. If you had a single string object, which was referenced from multiple places, this would be simple.
But it turns out, it isn’t…
foo = ‘string’ vs foo = new String(‘string’)
You may be aware that you can use either of these two syntaxes to declare a string in JavaScript:
var strLiteral = 'I am a string literal'; var strObject = new String('I am a string object'); |
But what’s the difference between these two?
With string literals, assigning the string to another variable copies it, but any properties assigned to it are lost:
var literal = 'hello'; literal.prop = 'xyz'; var copy = literal; console.log(copy.prop) -> undefined |
If you use new String, it’s again copied, but since it’s an object, it’ll copy the reference. As such, the properties of the object are still there:
var obj = new String('hello'); obj.prop = 'xyz'; var copy = obj; console.log(copy.prop); -> 'xyz' |
Maybe we can use this to our advantage?
Making string objects behave funny
If you’re familiar with String functions in JavaScript, you may recall that none of them actually modify the string itself – They all return a new string with the modifications.
How could we change a string, then?
Array to the rescue!
We can use array’s functions on strings to try and hack it:
var x = new String('foo'); //Try appending to string Array.prototype.push.call(x, ' ', 'b', 'a', 'r'); |
Turns out this doesn’t quite work as expected. If you output x
, you still get 'foo'
. However, if you iterate all properties in it, you will actually see the pushed characters.
Perhaps something else is affecting the output?
In JavaScript objects, the toString method is used to convert them into string representations. Perhaps if we override toString?
var x = new String('foo'); x.toString = function() { return 'foo bar'; }; console.log(x); -> 'foo' console.log(String(x)); -> 'foo bar' |
Turns out toString isn’t used when it comes to strings. However, when casting the variable to string with the String constructor, it outputs the value from toString.
Confusing is it not?
So what can we actually do?
It does look like strings can’t really be made mutable in a sensible way, doesn’t it?
But there is one more alternative: A custom string class.
We can create a MutableString class:
var MutableString = function(value) { this.text = value; }; MutableString.prototype = { toString: function() { return this.text; } }; var x = new MutableString('bar'); console.log(x); -> object var y = 'foo ' + x; console.log(y); -> foo bar |
In this case the variable appears as an object when logging, but the toString method gets called when it’s cast to string, such as when concatenating it with strings.
This example doesn’t show how you’d actually change a MutableString – You will need to add some methods that will allow changing the value of the text property to make any use of this as anything else than a string wrapper.
The usefulness of this exercise
In the end, this isn’t a very useful concept – Most of the time you don’t really need strings to be modifiable like this.
What we can learn from this is that JavaScript’s built-in functions are surprisngly versatile – We called a function from the Array object on a String, and it actually worked (but due to how string works it did not provide the expected results)
If you know something that I don’t, and are actually aware of how this could be done without silly hacks like the MutableString class, feel free to point it out in the comments!