オブジェクトがobj1 + obj2
に追加されるか、 obj1 - obj2
が減算されるか、またはalert(obj)
を使用して出力されるとどうなりますか?
JavaScript では、演算子がオブジェクトに対してどのように動作するかをカスタマイズすることはできません。 Ruby や C++ などの他のプログラミング言語とは異なり、加算 (または他の演算子) を処理するための特別なオブジェクト メソッドを実装することはできません。
このような操作の場合、オブジェクトはプリミティブに自動変換され、これらのプリミティブに対して操作が実行され、プリミティブ値が得られます。
これは重要な制限です。obj1 obj1 + obj2
(または別の算術演算) の結果を別のオブジェクトにすることはできません。
たとえば、ベクトルや行列 (または実績など) を表すオブジェクトを作成し、それらを追加して、結果として「合計された」オブジェクトを期待することはできません。このような建築上の偉業は自動的に「オフボード」になります。
したがって、ここでは技術的に多くのことはできないため、実際のプロジェクトではオブジェクトに関する数学は必要ありません。まれな例外を除いて、この問題が発生する場合は、コーディングミスが原因です。
この章では、オブジェクトをプリミティブに変換する方法と、それをカスタマイズする方法について説明します。
私たちには 2 つの目的があります。
Date
オブジェクト)。それらについては後で説明します。「型変換」の章では、プリミティブの数値、文字列、およびブール値の変換のルールについて説明しました。しかし、物体を入れるための隙間を残しました。さて、メソッドとシンボルについて理解したので、それを埋めることが可能になります。
true
です。数値と文字列の変換のみが存在します。Date
オブジェクト (「日付と時刻」の章で説明します) を減算すると、 date1 - date2
の結果が 2 つの日付間の時差になります。alert(obj)
や同様のコンテキストでオブジェクトを出力するときに発生します。特別なオブジェクト メソッドを使用して、文字列と数値の変換を自分で実装できます。
次に、技術的な詳細について説明します。これがこのトピックを詳しく説明する唯一の方法だからです。
JavaScript はどの変換を適用するかをどのように決定するのでしょうか?
型変換には 3 つのバリエーションがあり、さまざまな状況で発生します。仕様で説明されているように、それらは「ヒント」と呼ばれます。
"string"
オブジェクトから文字列への変換の場合、 alert
のような文字列を期待するオブジェクトに対して操作を行う場合:
// output alert(obj); // using object as a property key anotherObj[obj] = 123;
"number"
数学を行う場合など、オブジェクトから数値への変換の場合:
// explicit conversion let num = Number(obj); // maths (except binary plus) let n = +obj; // unary plus let delta = date1 - date2; // less/greater comparison let greater = user1 > user2;
ほとんどの組み込み数学関数には、このような変換も含まれています。
"default"
まれに、オペレーターがどのタイプを予期すればよいか「わからない」場合に発生します。
たとえば、バイナリ プラス+
、文字列 (文字列の連結) と数値 (文字列の加算) の両方を処理できます。したがって、バイナリ プラスが引数としてオブジェクトを取得すると、 "default"
ヒントを使用してそれを変換します。
また、 ==
使用してオブジェクトが文字列、数値、または記号と比較される場合も、どの変換を行うべきかが不明瞭であるため、 "default"
ヒントが使用されます。
// binary plus uses the "default" hint let total = obj1 + obj2; // obj == number uses the "default" hint if (user == 1) { ... };
<
>
などの大小比較演算子は、文字列と数値の両方で使用できます。それでも、 "default"
ではなく"number"
ヒントを使用します。それは歴史的な理由によるものです。
ただし、実際には、物事はもう少し単純です。
1 つのケース ( Date
オブジェクト、後で学習します) を除くすべての組み込みオブジェクトは、 "number"
と同じ方法で"default"
変換を実装します。そしておそらく私たちも同じことをすべきでしょう。
それでも、3 つのヒントをすべて知っておくことが重要です。その理由はすぐにわかります。
変換を行うために、JavaScript は 3 つのオブジェクト メソッドを見つけて呼び出そうとします。
obj[Symbol.toPrimitive](hint)
を呼び出します – シンボリック キーSymbol.toPrimitive
(システム シンボル) を持つメソッド (そのようなメソッドが存在する場合)"string"
の場合obj.toString()
またはobj.valueOf()
が存在するものであれば何でも呼び出してみてください。"number"
または"default"
の場合obj.valueOf()
またはobj.toString()
が存在するものであれば何でも呼び出してみてください。最初の方法から始めましょう。次のように、変換メソッドの名前を付けるために使用する必要があるSymbol.toPrimitive
という名前の組み込みシンボルがあります。
obj[Symbol.toPrimitive] = function(hint) { // here goes the code to convert this object to a primitive // it must return a primitive value // hint = one of "string", "number", "default" };
Symbol.toPrimitive
メソッドが存在する場合、それがすべてのヒントに使用されるため、それ以上のメソッドは必要ありません。
たとえば、ここではuser
オブジェクトがそれを実装しています。
let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // conversions demo: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500
コードからわかるように、 user
変換に応じて自己記述的な文字列または金額になります。単一メソッドuser[Symbol.toPrimitive]
すべての変換ケースを処理します。
Symbol.toPrimitive
がない場合、JavaScript はtoString
とvalueOf
メソッドを見つけようとします。
"string"
ヒントの場合: toString
メソッドを呼び出し、それが存在しない場合、またはプリミティブ値の代わりにオブジェクトを返す場合は、 valueOf
を呼び出します (文字列変換ではtoString
優先されます)。valueOf
呼び出し、それが存在しない場合、またはプリミティブ値の代わりにオブジェクトを返す場合は、 toString
を呼び出します (したがって、 valueOf
数学的に優先されます)。 toString
メソッドとvalueOf
メソッドは古くから存在します。これらはシンボルではなく (シンボルはそれほど昔には存在しませんでした)、むしろ「通常の」文字列名付きメソッドです。これらは、変換を実装するための代替の「古いスタイル」方法を提供します。
これらのメソッドはプリミティブ値を返す必要があります。 toString
またはvalueOf
オブジェクトを返した場合、それは無視されます (メソッドがない場合と同じです)。
デフォルトでは、プレーン オブジェクトには次のtoString
とvalueOf
メソッドがあります。
toString
メソッドは文字列"[object Object]"
を返します。valueOf
メソッドはオブジェクト自体を返します。デモは次のとおりです。
let user = {name: "John"}; alert(user); // [object Object] alert(user.valueOf() === user); // true
したがって、 alert
などでオブジェクトを文字列として使用しようとすると、デフォルトでは[object Object]
が表示されます。
デフォルトのvalueOf
、混乱を避けるため、完全を期すためにここでのみ言及されています。ご覧のとおり、オブジェクト自体が返されるため、無視されます。理由は聞かないでください、それは歴史的な理由によるものです。したがって、それは存在しないと仮定できます。
これらのメソッドを実装して変換をカスタマイズしましょう。
たとえば、ここではuser
Symbol.toPrimitive
の代わりにtoString
とvalueOf
の組み合わせを使用して上記と同じことを行います。
let user = { name: "John", money: 1000, // for hint="string" toString() { return `{name: "${this.name}"}`; }, // for hint="number" or "default" valueOf() { return this.money; } }; alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500
ご覧のとおり、動作はSymbol.toPrimitive
を使用した前の例と同じです。
多くの場合、すべての基本的な変換を単一の「キャッチオール」場所で処理したいと考えます。この場合、次のようにtoString
のみを実装できます。
let user = { name: "John", toString() { return this.name; } }; alert(user); // toString -> John alert(user + 500); // toString -> John500
Symbol.toPrimitive
とvalueOf
が存在しない場合、 toString
すべてのプリミティブ変換を処理します。
すべてのプリミティブ変換メソッドについて知っておくべき重要な点は、必ずしも「ヒント付き」プリミティブを返すとは限らないということです。
toString
正確に文字列を返すかどうか、またはSymbol.toPrimitive
メソッドがヒント"number"
の数値を返すかどうかは制御できません。
唯一必須のこと: これらのメソッドはオブジェクトではなくプリミティブを返さなければなりません。
歴史的な理由により、 toString
またはvalueOf
オブジェクトを返した場合、エラーは発生しませんが、そのような値は無視されます (メソッドが存在しなかった場合と同様)。それは、昔の JavaScript には適切な「エラー」という概念がなかったからです。
対照的に、 Symbol.toPrimitive
はより厳密で、プリミティブを返さなければなりません。そうでない場合はエラーが発生します。
すでにご存知のとおり、多くの演算子と関数は型変換を実行します。たとえば、乗算*
はオペランドを数値に変換します。
オブジェクトを引数として渡す場合、計算には 2 つの段階があります。
例えば:
let obj = { // toString handles all conversions in the absence of other methods toString() { return "2"; } }; alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number
obj * 2
は、まずオブジェクトをプリミティブ (文字列"2"
) に変換します。"2" * 2
は2 * 2
になります (文字列は数値に変換されます)。Binary plus は文字列を喜んで受け入れるため、同じ状況で文字列を連結します。
let obj = { toString() { return "2"; } }; alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation
オブジェクトからプリミティブへの変換は、プリミティブを値として期待する多くの組み込み関数および演算子によって自動的に呼び出されます。
3種類(ヒント)あります。
"string"
(文字列を必要とするalert
およびその他の操作の場合)"number"
(数学用)"default"
(演算子はほとんどありません。通常、オブジェクトは"number"
と同じ方法で実装します)仕様では、どの演算子がどのヒントを使用するかが明示的に説明されています。
変換アルゴリズムは次のとおりです。
obj[Symbol.toPrimitive](hint)
を呼び出します。"string"
の場合obj.toString()
またはobj.valueOf()
が存在するものであれば何でも呼び出してみてください。"number"
または"default"
の場合obj.valueOf()
またはobj.toString()
が存在するものであれば何でも呼び出してみてください。これらすべてのメソッドは、(定義されている場合) 動作するためにプリミティブを返す必要があります。
実際には、ロギングやデバッグの目的でオブジェクトの「人間が判読できる」表現を返す文字列変換の「キャッチオール」メソッドとしてobj.toString()
のみを実装するだけで十分なことがよくあります。