JavaScript
ECMAScript

JavaScriptのプリミティブへの変換を完全に理解する

JavaScriptでは、オブジェクトからプリミティブへの暗黙の変換が発生することがあります1。その結果、例えば次のような楽しい事態が生じます。

console.log(["foo", "bar"] == "foo,bar"); // true
console.log([""] == 0);                   // true
console.log((123 ^ {}) === 123);          // true

const obj1 = ["😂"];
const obj2 = ["😂"];
console.log(obj1 == "😂", "😂" == obj2);   // true true
console.log(obj1 == obj2);                // false

このような挙動は面白いので、Twitterとかで誰かが話題にするたびに多少は話題になります。しかしいい加減飽きたので、皆さんにはこんなの常識として理解しておいていちいち騒がないでいただきたく、この記事を用意しました。

この記事では、JavaScriptにおけるプリミティブ変換に関する仕様を余すこと無く紹介します。JavaScriptの経験が無い人には細部の理解が難しいかもしれませんが、それでも雰囲気くらいは掴めるのではないかと思います。

※本記事の内容は執筆時点の仕様(ES2017)に基づくものです。

明示的な変換

まず手始めに、オブジェクトをプリミティブに変換する明示的な方法を紹介します。オブジェクトは、文字列・数値・真偽値の3種類のプリミティブに変換することができます。そのために、String, Number, Booleanというビルトイン関数を使用します。

明示的な変換の例
console.log(String({foo: "bar"}));  // "[object Object]"
console.log(String(["12", 345]));   // "12,345"

console.log(Number({foo: "bar"}));  // NaN
console.log(Number(["123"]));       // 123

console.log(Boolean({foo: "bar"})); // true
console.log(Boolean(["0"]));        // true

この例がなぜこのような結果になるのかは、後々解説します。

暗黙の変換

この記事では、上で紹介した方法以外は暗黙の変換として扱うことにします。オブジェクトからプリミティブへの暗黙の変換は、色々な場面で発生します。

一つは、==演算子でオブジェクトとプリミティブを比較した場合です。例えば["foo", "bar"] == "foo,bar"という比較を行った場合、左辺がオブジェクト(配列)、右辺が文字列なので、オブジェクトが文字列に変換されます。この場合左辺の配列が"foo,bar"という文字列に変換されるので比較の結果はtrueになります。

ちなみに、JavaScriptを書く読者の方は恐らくご存知の通り、==演算子はJavaScriptにおいてはたいへん悪名高い演算子です。その理由はまさにこの暗黙の型変換を行うからです。より安全な比較演算子である===は暗黙の型変換を行いません。オブジェクトと文字列を比較した時点で異なる値として扱われ、結果はfalseとなります。

また、-^など、数値同士の演算を行う演算子をオブジェクトに適用した場合も、やはりオブジェクトが数値に変換されます。例えば["123"] - ["12"]という式は両辺がオブジェクト(配列)ですが、左辺が123、右辺が12に変換されるため、結果は111という数値になります。

他にはプロパティ名として使用した場合(obj[ ["123"] ])やテンプレート文字列中に出現した場合(`123${obj}9`)、Number.parseIntの引数として与えられた場合など、さまざまな場面でオブジェクトはプリミティブに変換されます。

全ての場合を列挙するのは大変なので、本当に完全に理解したいという方は仕様書を"ToPrimitive", "ToString", "ToNumber"などで全文検索してください。

プリミティブへの変換の種類

実は、オブジェクトからプリミティブへの変換は3種類あります。これは変換時に付随するhint値というパラメータによって区別されます。hint値には、変換後のプリミティブとして文字列が期待されていることを表すstringと、数値が期待されていることを表すnumberと、特に期待する型がないdefaultの3種類があります。
例えば、obj * 10という式でobjがオブジェクトだった場合を考えます。*は算術演算子でありオペランドとして数値が期待されますから、objは数値に変換される必要があります。よって、objはhint値がnumberでプリミティブに変換されます。

同様に、JavaScriptではプロパティ名は文字列(またはシンボル)ですから、プロパティのキーとしてオブジェクトを使用した場合は、オブジェクトは文字列に変換されることが期待されるため、hint値がstringでプリミティブに変換されます。

hint値defaultは、==演算子と+のオペランドにオブジェクトが来た場合に使用されます。これらの演算子は文字列と数値の両方を取ることができるためです。

プリミティブへの変換の挙動

以上の説明で、どのような場合にオブジェクトがプリミティブに変換されるのか、そしてプリミティブへの変換には実は3種類あることが分かりました。ではここから、オブジェクトからプリミティブへの変換が発生した場合に具体的にどのような挙動が起こるのかを解説します。仕様書読めるよという方は(そんな人はこの記事を読まないとは思いますが)、ToPrimitiveのことです。

オブジェクトのプリミティブへの変換は、toString, valueOfという2つのメソッドを呼び出すことで行われます。具体的には、hint値がstringの場合、すなわち文字列への変換が期待されている場合にはオブジェクトのtoStringメソッドを呼び出し、それ以外の場合(hint値がnumberdefaultの場合)はvalueOfメソッドを呼び出します。もちろん、メソッドの返り値がプリミティブへの変換結果として使用されます。

例えば{}を文字列に変換すると"[object Object]"になるのは、Object.prototype.toStringの定義によるものです2 。独自のtoStringメソッドを持ったオブジェクトを作ることにより、文字列に変換したときの挙動をカスタマイズできます。

toStringをカスタマイズしたオブジェクトの例
var obj = {
  toString() {
    return "world";
  }
};

console.log(`Hello, ${obj}!`); // "Hello, world!"

一方で、数値への変換(及びデフォルトの場合)はvalueOfメソッドが担当することになります。よって、valueOfメソッドをカスタマイズしたオブジェクトを作ると、数値に変換されたときの挙動を制御できます。

valueOfをカスタマイズしたオブジェクトの例
var obj = {
  valueOf() {
    return 123;
  }
};

console.log(obj * 10);   // 1230
console.log(obj == 123); // true

変換失敗時のフォールバック

ところで、一般のオブジェクトのvalueOfの実装(すなわちObject.prototype.valueOf)は、概ね次のような感じです(厳密にはちょっと異なりますが)。

Object.prototype.valueOf
valueOf() {
  return this;
}

ふざけてんのかと言いたくなりますね。プリミティブに変換しようという気概がまったく感じられません。このように、toStringやvalueOfの返り値というのは必ずしもプリミティブになるとは限りません。

このような場合、プリミティブへの変換に失敗したと見なされます。そして、その場合はもう一方のメソッドを試すというフォールバックの挙動が起こります。すなわち、valueOfでプリミティブに変換しようとして失敗した場合、次にtoStringを試すのです。先にtoStringを試して失敗した場合も、やはり次にvalueOfを試します。

フォールバックが起きる例
var obj = {
  toString() {
    return 100;
  },
  valueOf() {
    return this;
  },
};

console.log(obj * 123); // 12300

上の例では、obj * 123なのでobjをまずvalueOfでプリミティブに変換しようとします。しかし、valueOfの返り値はオブジェクトなのでプリミティブへの変換に失敗します。なので、次にtoStringが呼び出され、その返り値が変換結果として採用されます。今回のオブジェクトはtoStringが数値を返すというなんとも厄介なオブジェクトですが、これによりobjを数値に変換した結果が100となります。

また、そもそもtoStringやvalueOfメソッドが存在しない(あるいは関数でない)場合も失敗扱いとなり、フォールバックが発生します。

toStringが存在しない例
var obj = {
  toString: null,
  valueOf() {
    return "foobar";
  },
};

console.log(String(obj)); // "foobar"

この例ではobjを文字列に変換しようとしていますが、toStringがnullになっているためvalueOfにフォールバックし、valueOfの返り値である"foobar"が変換結果となります。

そして、toStringもvalueOfも両方失敗した場合はプリミティブへの変換が失敗したということになり、エラーが発生します。

プリミティブへの変換に失敗する例
var obj = {
  toString: null,
};
console.log(obj == 3); // TypeError

普通のオブジェクトのvalueOfはもともと失敗するので、toStringを無効化してやるとプリミティブへの変換に失敗するオブジェクトを作ることができます。

プリミティブ型どうしの変換

先ほど、toStringの返り値が数値になる変なオブジェクトを作りました。hint値がstringでオブジェクトをプリミティブに変換した場合はtoStringが優先されます。そして、toStringの返り値が何らかのプリミティブであれば、それが文字列でなかったとしてもプリミティブへの変換は成功として扱われます。ということは、hint値がstringでオブジェクトをプリミティブに変換した場合でもその結果が文字列とは限らないわけです。

どうしても文字列に変換したい場合(仕様書用語でいうとToStringで変換した場合)は、得られたプリミティブがさらに文字列に変換されます。どうしても数値が欲しい場合も同様です。

toStringが真偽値を返す例
var obj = {
  toString() {
    return true;
  }
};

console.log(String(obj)); // "true"

この例は、toStringメソッドがtrueを返すふざけたメソッドです。Stringによる明示的な変換では結果はかならず文字列になる必要がありますから、objをプリミティブに変換した結果として得られたtrueはさらに文字列に変換されて"true"となります。

数値へ変換する場合も同様です。

toStringを経由して数値に変換する例
var obj = {
  toString() {
    return "123";
  },
};

console.log(Number(obj)); // 123

この例ではobjを数値に変換した結果が123となっています。上で解説したように、オブジェクトを数値に変換したい場合はvalueOfがまず呼ばれますがそれは失敗し、toStringの結果が用いられます。toStringの結果は"123"という文字列ですが、今回は数値が欲しいのでこの文字列が数値に変換され、123が結果となります。

プリミティブへの変換の結果が尊重される場合

大抵の場合、オブジェクトをプリミティブに変換する場合は特定の型(文字列とか数値とか)が目当てなので、前節で説明したようにオブジェクトをプリミティブに変換した結果が目的の型とあわない場合はさらに目的の型に変換されます。

しかし、オブジェクトをプリミティブに変換した結果が尊重される場合が少数あります。それは、+により変換される場合、==<などの比較演算子で変換される場合、Dateコンストラクタにオブジェクトを渡した場合、そしてプロパティ名として使用する場合です。これらの演算子等は複数の種類のプリミティブ(文字列・数値)を扱うことができるためこのような挙動になっています。

+による変換

+はどちらか一方のオペランドが文字列の場合は文字列の結合となり、そうでない場合は数値の加算となります。オペランドにオブジェクトが渡されたときの挙動は、とりあえずオブジェクトをプリミティブに(hint値defaultで)変換して、その結果どちらか一方が文字列なら文字列の結合となります。

+による変換の例
var obj1 = {
  toString() {
    return "123";
  },
};
var obj2 = {
  valueOf() {
    return 123;
  }
};
var obj3 = {
  toString() {
    return true;
  }
};

console.log(obj1 + 321); // "123321"
console.log(obj2 + 321); // 444
console.log(obj3 + 321); // 322

この例では、obj1をhint値defaultでプリミティブに変換すると"123"となるため+は文字列の結合となり、結果は"123321"となります。一方obj2は数値123に変換されるため+は数値の加算となり、結果が444となります。obj3の変換結果は真偽値trueですが、この場合+は数値の加算となるためtrueが数値に変換されて1となり、結果が322になります。

==による変換

比較演算子==の場合は少し挙動が複雑です。まず、オブジェクト同士を比較した場合はプリミティブへの変換は起こりません。===と同様に同じオブジェクトの場合のみtrueになります。プリミティブへの変換が起こるのは、オブジェクトとプリミティブの比較をした場合です。この場合オブジェクトはhint値defaultでプリミティブに変換され、両辺が文字列の場合のみ文字列の比較となります。片方でも数値や真偽値がある場合は数値の比較となります。

==による変換の例
var obj1 = {
  toString() {
    return "hello";
  },
};
var obj2 = {
  toString() {
    return "1e3";
  }
};

console.log(obj1 == "hello"); // true
console.log(obj2 == "1000");  // false
console.log(obj2 == 1000);    // true

この例では、obj1"hello"を比較しようとしているのでobj1がプリミティブに変換されて"hello"になり、文字列の比較により結果はtrueとなります。obj2"1000"を比較する場合も同様に文字列の比較となり、"1e3""1000"は違う文字列なのでfalseとなります。

興味深いのは最後の比較ですね。この場合obj21000を比較しようとしているのでまずobj2がプリミティブに変換され"1e3"1000の比較になります。文字列と数値の比較なので左辺の文字列は数値に変換されます。実は"1e3"という文字列は数値に変換すると1000になるため、この比較はtrueとなります。

その他の比較演算子による変換

<などの比較演算子の場合も同様に、両辺が文字列の場合のみ文字列の比較となり、それ以外の場合は数値の比較となります。==の場合と異なるのは、両辺がオブジェクトの場合でもプリミティブへの変換が行われることと、その際のhint値はnumberとなることです。

プロパティ名として使用する場合の変換

オブジェクトをプロパティ名として用いる場合はやや特殊な挙動を示します。それは、プロパティ名としては文字列だけでなくシンボルを使用することができるからです。

オブジェクトがプロパティ名として使用された場合、まずhint値stringでプリミティブに変換されます。結果が文字列またはシンボルの場合はそれが採用され、それ以外の場合は結果が文字列に変換されます。

プリミティブに変換するとシンボルになる例
var symb = Symbol();
var obj = {
  toString() {
    return symb;
  }
};
var obj2 = {
  [symb] : "hi"
};

console.log(obj2[obj]); // "hi"

しかし、プリミティブに変換するとシンボルになるオブジェクトは扱いにくいです。シンボルは文字列や数値に変換できないからです。

シンボルをさらに変換しようとしてエラーになる例
var symb = Symbol();
var obj = {
  toString() {
    return symb;
  }
};

console.log(`Hello, ${obj}!`); // TypeError

[Symbol.toPrimitive]メソッド

ここまでで、オブジェクトをプリミティブに変換する場合はtoString, valueOfメソッドが使用されることや、それに関連する挙動を紹介しました。

しかし、ES2015以降では、オブジェクトからプリミティブへの変換をより細かに制御する方法があります。それが、[Symbol.toPrimitive]メソッドを使う方法です。聡明な読者の皆さんは、ここまで読んである問題に気がついたはずです。それは、プリミティブへの変換時のhint値にはstring, number, defaultの3種類があるのに、numberdefaultは挙動の違いが無いという点です。[Symbol.toPrimitive]を使うことで、この2つに異なる挙動を与えることができます。

プリミティブに変換しようとしているオブジェクトに[Symbol.toPrimitive]メソッドが存在する場合、toStringやvalueOfメソッドを呼ぶ代わりにこのメソッドを呼び、その返り値がプリミティブへの変換の結果として採用されます。その際第1引数にhint値が文字列で与えられます。

[Symbol.toPrimitive]による変換の例
var obj = {
  [Symbol.toPrimitive](hint) {
    if (hint === "string") {
      return "world";
    } else if (hint === "default") {
      return 100;
    } else {
      return true;
    }
  },
};

console.log(`Hello, ${obj}!`); // "Hello, world!"
console.log(obj == 100);       // true
console.log(obj * 10);         // 10
console.log(obj == "world");   // false

この例のobjは、3種類のhint値に対して異なる値を返すというはた迷惑なオブジェクトです。obj == "world"についてはfalseとなる点に注意してください。==は常にhint値defaultでオブジェクトをプリミティブに変換するからです。

ちなみに、[Symbol.toPrimitive]がプリミティブ以外を返した場合(すなわちオブジェクトを返した場合)、プリミティブへの変換は失敗とみなされエラーが発生します。

[Symbol.toPrimitive]の結果がエラーを発生させる例
var obj = {
  [Symbol.toPrimitive]() {
    return this;
  },
};

console.log(obj == 100); // TypeError

組み込みオブジェクトを変換した場合の挙動

この記事の冒頭の例では配列が多く出てきました。これは配列をプリミティブに変換したときの挙動がちょっと特殊だからです。

基本的には、組み込みオブジェクトをプリミティブに変換したときの挙動は、これまで紹介したtoString, valueOf, あるいは[Symbol.toPrimitive]によって制御されています。例えば、配列をプリミティブに変換したときの挙動はArray.prototype.toStringにより定義されています。Array.prototype.toStringの挙動はだいたいこうです。

Array.prototype.toStringの挙動
toString() {
  return this.join();
}

すなわち、配列の場合はtoStringjoinと同じということです。joinは配列の各要素をつなげた文字列を返すメソッドであり、区切り文字列を省略した場合は","となります。よって、例えば["foo", "bar"]を文字列に変換した場合は"foo,bar"となるのです。

なお、Array.prototype.valueOfは定義されていないので、Object.prototype.valueOfと同じく自分自身を返します。

配列の場合もtoStringによりプリミティブに変換されるということは、自分でtoStringを定義することでプリミティブへの変換がカスタマイズされた配列を作れるということです。

プリミティブへの変換をカスタマイズした配列の例
var obj = ["foo", "bar"];
obj.toString = ()=> "world";

console.log(`hello, ${obj}!`); // "hello, world"

多くの組み込みオブジェクトにおいて、こんな感じにtoStringがいい感じに定義されていたりいなかったりします。その中でひときわ異彩を放つのがDateオブジェクトです。

Dateオブジェクトは独自のtoStringとvalueOf(すなわちDate.prototype.toStringDate.prototype.valueOf)を持っています。toStringは何かいい感じの文字列を返し、valueOfは現在の時刻を表す数値(1970年1月1日からの経過時間をミリ秒で表した数値)を返します。

まず、ちゃんとvalueOfが数値を返すようになっているのがいいですね。これにより、Number(date)のようにして数値に変換した際にちゃんと意味のある数値になります。また、Dateオブジェクトをhint値defaultでプリミティブに変換するときの挙動が少し特殊で、この場合はtoStringを先に試します。一般のオブジェクトがhint値defaultの場合にはvalueOfを先に試すのとは対称的です。この挙動により、+で文字列とDateオブジェクトを結合したときにDateオブジェクトが数値でなく文字列になるようになっています。

Dateオブジェクトをhint値defaultでプリミティブに変換する例
var date = new Date();

console.log("現在の時刻は " + date + "です"); // "現在の時刻は Wed May 09 2018 11:55:41 GMT+0900 (JST)です"

ちなみに、Dateオブジェクトのこの特殊な挙動はDate.prototype[Symbol.toPrimitive]により定義されています。

[Symbol.toStringTag]について

ところで、普通のオブジェクトをtoStringでプリミティブに変換すると[object Object]になります。実は後半のObjectの部分はものによって変わります。例えばPromiseオブジェクトの場合は[object Promise]となります。

Promiseオブジェクトを文字列に変換する例
var p = Promise.resolve(123);

console.log(String(p)); // "[object Promise]"

この部分を決めているのが[Symbol.toStringTag]プロパティです。これをカスタマイズしたオブジェクトを作ることで、Object.prototype.toStringの結果を部分的にカスタマイズしたオブジェクトを作ることができます。

[Symbol.toStringTag]をカスタマイズする例
var obj = {
  [Symbol.toStringTag] : "Hello",
};

console.log(String(obj)); // "[object Hello]"

正直、だからどうしたという感想しか出てきませんね。組み込みオブジェクトを文字列に変換したときの結果を制御するために一役買っていますが、我々が使うことはあまりないかもしれません。文字列に変換したときの結果を手軽にカスタマイズしたいときに使ってください。一応、クラスを定義するときに次のように[Symbol.toStringTag]をカスタマイズしておくと、そのクラスのインスタンスを文字列に変換したときの結果を制御できていい感じです。

toStringの結果をカスタマイズしたクラスの例
class Hello {
  get [Symbol.toStringTag]() {
    return "Hello";
  }
}

var obj = new Hello();
console.log(String(obj)); // "[object Hello]"

なお、当然ながら、これはObject.prototype.toStringの処理に使われるものなので、toString自体をカスタマイズしたり[Symbol.toPrimitive]を使用したりした場合はほぼ無意味になります(Object.prototype.toString.call(obj)のように呼ばれたら使われるので完全に無意味ではありませんが)。

応用

お疲れさまでした。以上で、JavaScriptにおけるオブジェクトからプリミティブへの変換の解説は終わりです。最後に、このような挙動から分かる少し面白い例を紹介します。

冒頭の例について

今までの説明で、この記事の冒頭の例はほぼ分かると思います。例えば、[""] == 0がtrueとなるのはなぜでしょうか。これは次のように評価されます。

  1. オブジェクトとプリミティブの比較なので、左辺の[""]がhint値defaultでプリミティブに変換され、その結果"" == 0という比較になる。
  2. 文字列と数値の比較なので、左辺の文字列が数値に変換される。""を数値に変換すると0なので0 == 0という比較になる。
  3. 数値同士の比較は普通に値が等しい場合にtrueになるので、結果はtrueとなる。

このように、オブジェクトを数値に変換する場合、度々オブジェクト → 文字列 → 数値という2段階の変換になります。

(123 ^ {}) === 123についても解説が必要かもしれません。^はビットごとのXORです。

実は^のようなビット演算は少し特殊で、当然ながらオペランドは数値に変換されますが、それだけでなくこれらは32ビット整数になります。というのも、JavaScriptは数値側は(もうすぐ追加されるBigIntを除けば)1種類で、64bit浮動小数点数のみです。そのため、ビット演算を行うときはまず数値を32ビット整数の範囲に落としこむ必要があるのです。このことを念頭におくと、123 ^ {}は次のように評価されることが分かります。

  1. 123 ^ {}は数値の演算なので、右辺の{}を数値に変換したい。
  2. {}をhint値numberでプリミティブに変換すると"[object Object]"になる。
  3. 数値が欲しいので"[object Object]"を数値に変換すると、結果はNaNになる。
    • 文字列数値に変換する場合、数値として解釈できない文字列はNaNになるという点に注意してください。
  4. NaNを32ビット整数に変換すると0になる。
    • 基本的にNaNを含む演算はNaNになるのが浮動小数点数の演算の鉄則ですが、32ビット整数の世界にNaNは無いため0に変換されます。
  5. 123 ^ 0が評価されて123になる。

この例が少し面白いところは、一般のオブジェクトを数値に変換しようとするとNaNになってしまい面白くないところ、ビット演算をかませることで0に引き戻すことができ、オブジェクトを交えたよくわからない演算ができるという点です。

2種類の文字列結合は等価ではない

変数の値を交えた文字列を作りたい場合に古くから使われてきた方法は+による文字列結合です。すなわち、"こんにちは、 " + user + "さん"のような処理です。一方、ES2015からは便利なテンプレート文字列が導入されたため、これを`こんにちは、${user}さん` のように書くことができるようになりました。

この記事を読んだ皆さんならお分かりの通り、この2つは完全に等価ではありません。もちろん、userが文字列のときは大丈夫です。しかし、userがオブジェクトだった場合は違いが発生します。なぜなら、+の場合オブジェクトはhint値がdefaultでプリミティブに変換されるのに対し、テンプレート文字列の場合はhint値stringで変換されるからです。

2種類の方法で結果が異なる例
var user = {
  toString() {
    return "ジョン・スミス";
  },
  valueOf() {
    return "メアリー・スー";
  },
};

console.log("こんにちは、" + user + "さん"); // "こんにちは、メアリー・スーさん"
console.log(`こんにちは、${user}さん`);      // "こんにちは、ジョン・スミスさん"

==<で結果が食い違うオブジェクト

上で解説したように、==のオペランドはhint値defaultでプリミティブに変換され、<などその他の比較演算子の場合はhint値numberでプリミティブに変換されます。ということは、この2つで異なる数値を返せば==<で結果が食い違うオブジェクトを作れます。

==と<で結果が食い違うオブジェクトの例
var obj = {
  [Symbol.toPrimitive](hint) {
    return hint === "number" ? 0 : 100;
  }
};

console.log(obj == 100); // true
console.log(obj < 100);  // true

変換時に副作用があるオブジェクト

そもそも、オブジェクトからプリミティブへの変換時に関数が呼ばれるということは、副作用を仕込み放題ということです。少し前に話題になったStackOverflowの質問を覚えている方もいると思いますが、このように毎回プリミティブに変換した結果が違うオブジェクトを作ることができます。

毎回変換結果が違うオブジェクトの例
var obj = {
  value: 1,
  valueOf() {
    return this.value++;
  },
};

console.log(obj == 1 && obj == 2 && obj == 3); // true

結論

いかがでしたか。JavaScriptにおけるオブジェクトからプリミティブへの変換はなかなか奥が深いものがあります。言うまでもなく、このような珍妙な挙動の裏には古くから続くJavaScriptの後方互換性を保とうとする意図があります。

実際のところ、ここで紹介したような挙動が問題となるのはレアなケースであり、ほとんどの場面で暗黙の変換は大した問題もなく動作するでしょう。しかし、==+などを見るたびにこういった背景を思い浮かべながらコードを読むというのは労力の無駄というものです。ですから、できるだけプリミティブへの暗黙の変換は避けて安心して読めるコードを書くといいのではないかと思います。


  1. JavaScriptにおけるプリミティブとは基本的かつイミュータブルな値のことで、具体的には文字列・数値・真偽値・シンボル・null・undefinedです。それ以外(配列・関数なども含め)は全てオブジェクトです。 

  2. JavaScriptにあまり馴染みのない人は、これはオブジェクトのtoStringメソッドのデフォルト実装であると思うとよいでしょう。次の例のように独自のtoStringメソッドをプロパティとして持ったオブジェクトの場合、そちらがデフォルト実装よりも優先されます。