What is the difference between string primitives and String objects in JavaScript?

57,207

Solution 1

JavaScript has two main type categories, primitives and objects.

var s = 'test';
var ss = new String('test');

The single quote/double quote patterns are identical in terms of functionality. That aside, the behaviour you are trying to name is called auto-boxing. So what actually happens is that a primitive is converted to its wrapper type when a method of the wrapper type is invoked. Put simple:

var s = 'test';

Is a primitive data type. It has no methods, it is nothing more than a pointer to a raw data memory reference, which explains the much faster random access speed.

So what happens when you do s.charAt(i) for instance?

Since s is not an instance of String, JavaScript will auto-box s, which has typeof string to its wrapper type, String, with typeof object or more precisely s.valueOf(s).prototype.toString.call = [object String].

The auto-boxing behaviour casts s back and forth to its wrapper type as needed, but the standard operations are incredibly fast since you are dealing with a simpler data type. However auto-boxing and Object.prototype.valueOf have different effects.

If you want to force the auto-boxing or to cast a primitive to its wrapper type, you can use Object.prototype.valueOf, but the behaviour is different. Based on a wide variety of test scenarios auto-boxing only applies the 'required' methods, without altering the primitive nature of the variable. Which is why you get better speed.

Solution 2

This is rather implementation-dependent, but I'll take a shot. I'll exemplify with V8 but I assume other engines use similar approaches.

A string primitive is parsed to a v8::String object. Hence, methods can be invoked directly on it as mentioned by jfriend00.

A String object, in the other hand, is parsed to a v8::StringObject which extends Object and, apart from being a full fledged object, serves as a wrapper for v8::String.

Now it is only logical, a call to new String('').method() has to unbox this v8::StringObject's v8::String before executing the method, hence it is slower.


In many other languages, primitive values do not have methods.

The way MDN puts it seems to be the simplest way to explain how primitives' auto-boxing works (as also mentioned in flav's answer), that is, how JavaScript's primitive-y values can invoke methods.

However, a smart engine will not convert a string primitive-y to String object every time you need to call a method. This is also informatively mentioned in the Annotated ES5 spec. with regard to resolving properties (and "methods"¹) of primitive values:

NOTE The object that may be created in step 1 is not accessible outside of the above method. An implementation might choose to avoid the actual creation of the object. [...]

At very low level, Strings are most often implemented as immutable scalar values. Example wrapper structure:

StringObject > String (> ...) > char[]

The more far you're from the primitive, the longer it will take to get to it. In practice, String primitives are much more frequent than StringObjects, hence it is not a surprise for engines to add methods to the String primitives' corresponding (interpreted) objects' Class instead of converting back and forth between String and StringObject as MDN's explanation suggests.


¹ In JavaScript, "method" is just a naming convention for a property which resolves to a value of type function.

Solution 3

In case of string literal we cannot assign properties

var x = "hello" ;
x.y = "world";
console.log(x.y); // this will print undefined

Whereas in case of String Object we can assign properties

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world

Solution 4

String Literal:

String literals are immutable, which means, once they are created, their state can't be changed, which also makes them thread safe.

var a = 's';
var b = 's';

a==b result will be 'true' both string refer's same object.

String Object:

Here, two different objects are created, and they have different references:

var a = new String("s");
var b = new String("s");

a==b result will be false, because they have different references.

Solution 5

If you use new, you're explicitly stating that you want to create an instance of an Object. Therefore, new String is producing an Object wrapping the String primitive, which means any action on it involves an extra layer of work.

typeof new String(); // "object"
typeof '';           // "string"

As they are of different types, your JavaScript interpreter may also optimise them differently, as mentioned in comments.

Share:
57,207
The Alpha
Author by

The Alpha

I'm Sheikh Heera, a very simple guy who loves to take challenges and learning new things. StackOverflow is the perfect place for learning by helping others. I really enjoy my work and it's not only my profession but also my passion. Currently I'm working as CTO at AuthLab - WPManageNinja. Also, you may visit my blog on heera.it. #SOreadytohelp

Updated on October 06, 2021

Comments

  • The Alpha
    The Alpha over 2 years

    Taken from MDN

    String literals (denoted by double or single quotes) and strings returned from String calls in a non-constructor context (i.e., without using the new keyword) are primitive strings. JavaScript automatically converts primitives to String objects, so that it's possible to use String object methods for primitive strings. In contexts where a method is to be invoked on a primitive string or a property lookup occurs, JavaScript will automatically wrap the string primitive and call the method or perform the property lookup.

    So, I thought (logically) operations (method calls) on string primitives should be slower than operations on string Objects because any string primitive is converted to string Object (extra work) before the method being applied on the string.

    But in this test case, the result is opposite. The code block-1 runs faster than the code block-2, both code blocks are given below:

    code block-1 :

    var s = '0123456789';
    for (var i = 0; i < s.length; i++) {
      s.charAt(i);
    }
    

    code block-2 :

    var s = new String('0123456789');
    for (var i = 0; i < s.length; i++) {
        s.charAt(i);
    }
    

    The results varies in browsers but the code block-1 is always faster. Can anyone please explain this, why the code block-1 is faster than code block-2.

  • Yuriy Galanter
    Yuriy Galanter almost 11 years
    How come the string primitive inherits all prototype properties including custom String.prototype ones?
  • jfriend00
    jfriend00 almost 11 years
    @YuriyGalanter - Why do you think the primitive has all those prototype properties? Are you sure it isn't being implicitly being converted to an object by the code that is inspecting it? When I look at a string primitive in the javascript debugger, it doesn't have any properties. It's just a string primitive.
  • The Alpha
    The Alpha almost 11 years
    var s = '0123456789'; is a primitive value, how can this value have methods, I'm confused!
  • Paul S.
    Paul S. almost 11 years
    @YuriyGalanter internally, JavaScript treats everything as structures rather than literals, the structure for a DOMString has a "this is a string" flag, associating it with String.prototype
  • jfriend00
    jfriend00 almost 11 years
    @SheikhHeera - primitives are built into the language implementation so the interpreter can give them special powers.
  • The Alpha
    The Alpha almost 11 years
    @jfriend00, if it says string literals are converted to string Object, then what does it mean ?
  • jfriend00
    jfriend00 almost 11 years
    @SheikhHeera - I don't understand your last comment/question. A string primitive by itself does not support you adding your own properties to it. In order to allow that, javascript also has a String object which has all the same methods as a string primitive, but is a full-blown object that you can treat like an object in all ways. This dual form appears to be a bit messy, but I suspect it was done as a performance compromise since the 99% case is the use of primitives and they can probably be faster and more memory efficient than string objects.
  • Fabrício Matté
    Fabrício Matté almost 11 years
    @SheikhHeera "converted to string Object" is how MDN expresses it to explain how primitives are able to invoke methods. They aren't literally converted to string Objects.
  • The Alpha
    The Alpha almost 11 years
    @FabrícioMatté, so they are not physically not converted to an Object, right ?
  • Fabrício Matté
    Fabrício Matté almost 11 years
    @SheikhHeera In most cases, they are already objects, considering interpreters' implementations.
  • The Alpha
    The Alpha almost 11 years
    I thought it's something like this happens, i.e. var s='me'; and when I call me.charAt(1);, it does 'new String(me) first.
  • jfriend00
    jfriend00 almost 11 years
    @SheikhHeera - no. Did you read my answer? The javascript interpreter can invoke string methods directly on a string primitive without converting it to a full-blown javascript object.
  • Fabrício Matté
    Fabrício Matté almost 11 years
    @SheikhHeera That's how it is often explained to reduce confusion, but as you can see, that is rather unnecessary and an interpreter wouldn't generate a new object for no good reason. I might take a shot at connecting some loose bits in these answers.
  • Paul S.
    Paul S. almost 11 years
    @SheikhHeera no, a JavaScript engine usually compiles 'me' like (pseudocode) {type: ENUM.STRING, data: b"\x6d\x65"}
  • Fabrício Matté
    Fabrício Matté almost 11 years
    You're welcome. =] Now I'm wondering whether MDN's explanation is there just because it seems to be the easiest way to understand auto-boxing or whether there's any reference to it in the ES spec.. Reading throughout the spec at the moment to check, will remember to update the answer if I ever find a reference.
  • Thalaivar
    Thalaivar almost 11 years
    hmm interesting comments and answers, thought of answering this question... btw superb @jfriend00
  • Ben
    Ben almost 8 years
    Great insight into the V8's implementation. I shall add that boxing is not just there to resolve the function. It is also there to pass in the this reference into the method. Now I'm not sure whether V8 skips this for built-in methods but if you add your own extension to say String.prototype you will get a boxed version of the string object every time it's called.
  • Ciprian Tomoiagă
    Ciprian Tomoiagă over 7 years
    Finally someone motivates why we need String objects as well. Thank you!
  • Yang Wang
    Yang Wang almost 7 years
    Is string object immutable as well?
  • ruX
    ruX over 6 years
    @YangWang that's a silly language, for both a & b try to assign a[0] = 'X' it will be executed successfully but won't work as you might expect
  • The Alpha
    The Alpha almost 6 years
    Thanks for your input :-)
  • Aditya
    Aditya over 5 years
    Why would anyone have the need to do this ?
  • EyuelDK
    EyuelDK over 5 years
    Wow, that's some really weird behavior. You should add a small in-line demo in your comment to show this behavior - it's extremely eye opening.
  • The Alpha
    The Alpha almost 5 years
    Thanks for adding value to the question after quite a long time :-)
  • Félix Brunet
    Félix Brunet almost 5 years
    this is normal, if you think about it. new String("") return an object, and eval only evaluate string, et return every thing else as it is
  • The Alpha
    The Alpha about 4 years
    Thanks for your answer.
  • SC1000
    SC1000 almost 4 years
    You wrote "var a = 's'; var b = 's'; a==b result will be 'true' both string refer's same object." That's not correct: a and b do not refer to any same object, the result is true because they have the same value. Those values are stored in different memory locations, that's why if you change one the other doesn't change!
  • besserwisser
    besserwisser over 3 years
    But also: var a = String("s"); var b = String("s"); console.log(a == b); // returns true
  • TeaCoder
    TeaCoder about 2 years
    This answer is not correct. a and b in the first example are NOT the same object. What you are doing is a value comparison which is true but those two different objects in memory.
  • geoctrl
    geoctrl about 2 years
    @Aditya check out kremling's helper methods: kremling.js.org/api/always-maybe-and-toggle.html - it uses string objects to allow method chaining to build dynamic classNames in react.