147

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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
The Alpha
  • 143,660
  • 29
  • 287
  • 307

12 Answers12

181

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.

Samuel Goldenbaum
  • 18,391
  • 17
  • 66
  • 104
flavian
  • 28,161
  • 11
  • 65
  • 105
38

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.

Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166
  • 1
    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. – Fabrício Matté Jun 22 '13 at 23:59
  • 1
    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. – Ben Jun 07 '16 at 02:12
20

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
refactor
  • 13,954
  • 24
  • 68
  • 103
16

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.

Tot Zam
  • 8,406
  • 10
  • 51
  • 76
  • 1
    Is string object immutable as well? – Yang Wang Jul 09 '17 at 06:31
  • @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 – ruX Jan 05 '18 at 12:47
  • 1
    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! – ingdc Jul 10 '20 at 08:51
  • But also: var a = String("s"); var b = String("s"); console.log(a == b); // returns true – besserwisser Oct 30 '20 at 16:46
  • 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. – TeaCoder Feb 12 '22 at 16:42
12

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.

Community
  • 1
  • 1
Paul S.
  • 64,864
  • 9
  • 122
  • 138
7

When you declare:

var s = '0123456789';

you create a string primitive. That string primitive has methods that let you call methods on it without converting the primitive to a first class object. So your supposition that this would be slower because the string has to be converted to an object is not correct. It does not have to be converted to an object. The primitive itself can invoke the methods.

Converting it to an full-blown object (which allows you to add new properties to it) is an extra step and does not make the string oeprations faster (in fact your test shows that it makes them slower).

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • How come the string primitive inherits all prototype properties including custom `String.prototype` ones? – Yuriy Galanter Jun 22 '13 at 23:17
  • @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. – jfriend00 Jun 22 '13 at 23:20
  • 1
    `var s = '0123456789';` is a primitive value, how can this value have methods, I'm confused! – The Alpha Jun 22 '13 at 23:20
  • @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` – Paul S. Jun 22 '13 at 23:21
  • 2
    @SheikhHeera - primitives are built into the language implementation so the interpreter can give them special powers. – jfriend00 Jun 22 '13 at 23:21
  • @jfriend00, if it says string literals are converted to string Object, then what does it mean ? – The Alpha Jun 22 '13 at 23:21
  • 1
    @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. – jfriend00 Jun 22 '13 at 23:24
  • 1
    @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. – Fabrício Matté Jun 22 '13 at 23:30
  • @FabrícioMatté, so they are not physically not converted to an Object, right ? – The Alpha Jun 22 '13 at 23:31
  • @SheikhHeera In most cases, they **are** already objects, considering interpreters' implementations. – Fabrício Matté Jun 22 '13 at 23:32
  • 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. – The Alpha Jun 22 '13 at 23:35
  • @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. – jfriend00 Jun 22 '13 at 23:36
  • @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. – Fabrício Matté Jun 22 '13 at 23:36
  • @SheikhHeera no, a _JavaScript_ engine usually compiles `'me'` like (pseudocode) `{type: ENUM.STRING, data: b"\x6d\x65"}` – Paul S. Jun 22 '13 at 23:37
  • hmm interesting comments and answers, thought of answering this question... btw superb @jfriend00 – Thalaivar Jun 23 '13 at 00:00
5

I can see that this question has been resolved long ago, there is another subtle distinction between string literals and string objects, as nobody seems to have touched on it, I thought I'd just write it for completeness.

Basically another distinction between the two is when using eval. eval('1 + 1') gives 2, whereas eval(new String('1 + 1')) gives '1 + 1', so if certain block of code can be executed both 'normally' or with eval, it could lead to weird results

luanped
  • 3,178
  • 2
  • 26
  • 40
3

The existence of an object has little to do with the actual behaviour of a String in ECMAScript/JavaScript engines as the root scope will simply contain function objects for this. So the charAt(int) function in case of a string literal will be searched and executed.

With a real object you add one more layer where the charAt(int) method also are searched on the object itself before the standard behaviour kicks in (same as above). Apparently there is a surprisingly large amount of work done in this case.

BTW I don't think that primitives are actually converted into Objects but the script engine will simply mark this variable as string type and therefore it can find all provided functions for it so it looks like you invoke an object. Don't forget this is a script runtime which works on different principles than an OO runtime.

clearwater
  • 514
  • 2
  • 7
3

The biggest difference between a string primitive and a string object is that objects must follow this rule for the == operator:

An expression comparing Objects is only true if the operands reference the same Object.

So, whereas string primitives have a convenient == that compares the value, you're out of luck when it comes to making any other immutable object type (including a string object) behave like a value type.

"hello" == "hello"
-> true
new String("hello") == new String("hello") // beware!
-> false

(Others have noted that a string object is technically mutable because you can add properties to it. But it's not clear what that's useful for; the string value itself is not mutable.)

personal_cloud
  • 3,943
  • 3
  • 28
  • 38
1

The code is optimized before running by the javascript engine. In general, micro benchmarks can be misleading because compilers and interpreters rearrange, modify, remove and perform other tricks on parts of your code to make it run faster. In other words, the written code tells what is the goal but the compiler and/or runtime will decide how to achieve that goal.

Block 1 is faster mainly because: var s = '0123456789'; is always faster than var s = new String('0123456789'); due to the overhead of object creation.

The loop portion is not the one causing the slowdown because chartAt() can be inlined by the interpreter.

Try removing the loop and rerun the test, you will see the speed ratio will be the same as if the loop were not removed. In other words, for these tests, the loop blocks at execution time have exactly the same bytecode/machine code.

For these types of micro benchmarks, looking at the bytecode or machine code wil provide a clearer picture.

Electric Coffee
  • 11,733
  • 9
  • 70
  • 131
Arc
  • 167
  • 1
  • 4
1

In Javascript, primitive data types such is string is a non-composite building block. This means that they are just values, nothing more: let a = "string value"; By default there is no built-in methods like toUpperCase, toLowerCase etc...

But, if you try to write:

console.log(a.toUpperCase()); 
// or 
console.log(a.toLowerCase());

This will not throw any error, instead they will work as they should.

What happened ? Well, when you try to access a property of a string a Javascript coerces string to an object by new String(a); known as wrapper object.

This process is linked to concept called function constructors in Javascript, where functions are used to create new objects.

When you type new String('String value'); here String is function constructor, which takes an argument and creates an empty object inside the function scope, this empty object is assigned to this and in this case, String supplies all those known built in functions we mentioned before. and as soon as operation is completed, for example do uppercase operation, wrapper object is discarded.

To prove that, let's do this:

let justString = 'Hello From String Value';
justString.addNewProperty = 'Added New Property';
console.log(justString);

Here output will be undefined. Why ? In this case Javascript creates wrapper String object, sets new property addNewProperty and discards the wrapper object immediately. this is why you get undefined. Pseudo code would be look like this:

let justString = 'Hello From String Value';
let wrapperObject = new String( justString );
wrapperObject.addNewProperty = 'Added New Property'; //Do operation and discard
Electric Coffee
  • 11,733
  • 9
  • 70
  • 131
1

we can define String in 3-ways

  1. var a = "first way";
  2. var b = String("second way");
  3. var c = new String("third way");

An optional fourth way is doing

  1. var d = a + '';

Check the type of the strings created using typeof operator

  • typeof a // "string"
  • typeof b // "string"
  • typeof c // "object"

When comparing a and b:

a == b // true

When you compare String object:

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // result will be false, because they have different references
Electric Coffee
  • 11,733
  • 9
  • 70
  • 131