28

null and undefined don't have a toString or valueOf method. Afaik using String calls the toString method of its parameter (e.g. String({}) => [object Object]).

Why do String(null) or String(undefined work then? It doesn't implicitly do Object.prototype.toString.call(null). because that evaluates to [object Null].

[edit]: from the spec ECMA-262/5th edition (page 48). This doesn't add to clarification, I'd say:

/*
Table 13 — ToString Conversions  
-------------------------------------------------------------------------
Argument Type  | Result  
-------------------------------------------------------------------------
Undefined      | "undefined"
Null           | "null"  
Boolean        | If the argument is true, then the result is "true".
...            | ...
*/
KooiInc
  • 119,216
  • 31
  • 141
  • 177

4 Answers4

37

After reviewing my previous answer, it seems a complete overhaul of my previous answer is necessary. I was way over complicating it, as the short answer is that these are standards-specified special cases.

The specification for String() (String used as a function):

15.5.1.1 String ( [ value ] )

Returns a String value (not a String object) computed by ToString(value). If value is not supplied, the empty String "" is returned.

The ToString function (that exists internally, not in userland) is defined as follows (9.8):

"The abstract operation ToString converts its argument to a value of type String according to Table 13"

Argument Type | Result
Null | "null"
Undefined | "undefined"

This means that String(null) and String(undefined) go into this special table of types and just return the string values valued "null" and "undefined".

A user-land pseudo-implementation looks something like this:

function MyString(val) {
    if (arguments.length === 0) {
        return "";
    } else if (typeof val === "undefined") {
        return "undefined";
    } else if (val === null) {
        return "null";
    } else if (typeof val === "boolean") {
        return val ? "true" : "false";
    } else if (typeof val === "number") {
        // super complex rules
    } else if (typeof val === "string") {
        return val;
    } else {
        // return MyString(ToPrimitive(val, prefer string))
    }
}

(Note that this example ignores the constructor case (new MyString()) and that it uses user-land concepts rather than engine-land.)


I got a bit carried away and found an example implementation (V8 to be specific):

string.js:

// Set the String function and constructor.
%SetCode($String, function(x) {
  var value = %_ArgumentsLength() == 0 ? '' : TO_STRING_INLINE(x);
  if (%_IsConstructCall()) {
    %_SetValueOf(this, value);
  } else {
    return value;
  }
});

macros.py:

macro TO_STRING_INLINE(arg) = (IS_STRING(%IS_VAR(arg)) ? arg : NonStringToString(arg));

runtime.js:

function NonStringToString(x) {
  if (IS_NUMBER(x)) return %_NumberToString(x);
  if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
  if (IS_UNDEFINED(x)) return 'undefined';
  return (IS_NULL(x)) ? 'null' : %ToString(%DefaultString(x));
}

The NonStringToString (which is essentially what is of interest), is luckily defined in psuedo-JS-land. As you can see, there is indeed a special case for null/true/false/undefined.

Community
  • 1
  • 1
Corbin
  • 33,060
  • 6
  • 68
  • 78
  • String() !== String(undefined) is because in JavaScript two objects are equal only if they are the same object in memory. When calling String() you are creating a wrapper object around the string primitive so essentially the comparison is of two different objects wrapping the same string primitive. – Buzzy May 12 '14 at 07:13
  • @Buzzy Yes, for objects that is correct, and thus for things like `new String(x) === new String(y)`, that would make sense. `String(x)` is specified to be equivalent to a a string value though rather than a string object. According to the standard with regards to the `===` operator (11.9.6): "true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise, return false." – Corbin May 12 '14 at 07:28
  • 2
    @Buzzy I'm glad you commented. After rereading my old answer, I realized it was way overcomplicating what is a very clear cut functionality in the specification! :) – Corbin May 12 '14 at 07:57
  • 1
    makes this work, which is weird: `x={null:1}; x["null"];//1 x[null]; //1` – chiliNUT Mar 24 '15 at 21:28
2

There is probably just some extra checks and handling for special cases like null and undefined.

MDN says:

It's possible to use String as a "safer" toString alternative, as although it still normally calls the underlying toString, it also works for null and undefined.

Thilo
  • 257,207
  • 101
  • 511
  • 656
1

String(null) creates a string object and passes it a default value of null.

robrich
  • 13,017
  • 7
  • 36
  • 63
1

You might be interested in seeing the Annotated ES5 (which is much more readable than the ECMAScript 5 PDF) which states that: new String([ value ]) http://es5.github.com/#x15.5.2.1 calls [ToString] http://es5.github.com/#x9.8 (there is a table of the special convertion cases) to convert the value passed to it to a string.

Dan D.
  • 73,243
  • 15
  • 104
  • 123
  • 1
    It's the exact same table as I linked to, except for colored green in spots. (And a direct link) – Corbin Apr 28 '12 at 09:27