JavaScriptのvalueOf()とtoString()の比較


115

JavaScriptでは、すべてのオブジェクトにvalueOf()およびtoString()メソッドがあります。文字列変換が要求されるたびにtoString()メソッドが呼び出されたと思っていたかもしれませんが、どうやらvalueOf()によって切り捨てられているようです。

たとえば、コード

var x = {toString: function() {return "foo"; },
         valueOf: function() {return 42; }};
window.console.log ("x="+x);
window.console.log ("x="+x.toString());

印刷します

x=42
x=foo

これは逆に私を襲います...たとえば、xが複素数の場合、valueOf()にその大きさを与えたいですが、文字列に変換したいときはいつでも、 "a + bi"のようなものが必要になります。また、文字列を暗示するコンテキストで明示的にtoString()を呼び出す必要はありません。

これはその通りですか?


6
試しましたwindow.console.log (x);alert (x);
Li0liQ 2010年

5
それらはそれぞれ「オブジェクト」と「foo」を与えます。楽しいもの。
ブレインジャム2010年

実際、alert(x); 「foo」を与え、window.console.log(x); Firebugでは「foo {}」、Chromeコンソールではオブジェクト全体が表示されます。
ブレインジャム2010年

Firefox 33.0.2 alert(x)ではfoo、とがwindow.console.log(x)表示されますObject { toString: x.toString(), valueOf: x.valueOf() }
John Sonderson、2014年

回答:


107

( "x =" + x)が "x = tostring"ではなく "x = value"を与える理由は次のとおりです。「+」を評価する場合、JavaScriptはまずオペランドのプリミティブ値を収集し、次に各プリミティブのタイプに基づいて、加算または連結を適用する必要があるかどうかを決定します。

だから、これはあなたがそれがうまくいくと思う方法です

a + b:
    pa = ToPrimitive(a)
    if(pa is string)
       return concat(pa, ToString(b))
    else
       return add(pa, ToNumber(b))

これが実際に起こることです

a + b:
    pa = ToPrimitive(a)
    pb = ToPrimitive(b)*
    if(pa is string || pb is string)
       return concat(ToString(pa), ToString(pb))
    else
       return add(ToNumber(pa), ToNumber(pb))

つまり、toStringは元のオブジェクトではなく、valueOfの結果に適用されます。

詳細については、ECMAScript言語仕様のセクション11.6.1 加算演算子(+)を参照してください。


*文字列コンテキストで呼び出されると、ToPrimitive toStringを呼び出しますが、「+」はタイプコンテキストを強制しないため、ここでは当てはまりません。


3
「実際に」ブロックの条件文を「if(paは文字列&& pbは文字列)」と読むべきではありませんか?つまり、「||」の代わりに「&&」?
ブレインジャム2010年

3
規格は間違いなく「または」(リンクを参照)と述べています。
user187291 2010年

2
はい、その通りです。連結では、文字列が他の型よりも優先されます。場合はどちらかのオペランドが文字列で、全体の事は文字列として連結されます。いい答えだ。
devios1

76

答えを得る前に、もう少し詳しく説明します。

var x = {
    toString: function () { return "foo"; },
    valueOf: function () { return 42; }
};

alert(x); // foo
"x=" + x; // "x=42"
x + "=x"; // "42=x"
x + "1"; // 421
x + 1; // 43
["x=", x].join(""); // "x=foo"

toString機能がされていないことで、「捏造」valueOf一般的に。ECMAScript標準は実際にこの質問にかなりよく答えます。すべてのオブジェクトには、[[DefaultValue]]オンデマンドで計算されるプロパティがあります。このプロパティを要求すると、インタープリターは、期待する値の種類に関する「ヒント」も提供します。ヒントがある場合はString、そのtoString前に使用されますvalueOf。しかし、ヒントがされている場合Numberは、valueOf最初に使用されます。どちらか一方しか存在しない場合、またはプリミティブ以外を返す場合、通常はもう一方を2番目の選択肢として呼び出します。

+オペレータは、常にヒントを提供してNumber最初のオペランドが文字列値であったとしても、。最初のオペランドはから文字列を返すため、xそのNumber表現を要求しますが、文字[[DefaultValue]]列の連結を行います。

toString文字列連結のために呼び出されることを保証したい場合は、配列と.join("")メソッドを使用します。

+ただし、ActionScript 3.0はの動作をわずかに変更します。どちらかのオペランドがである場合String、それを文字列連結演算子として扱い、をString呼び出すときにヒントを使用します[[DefaultValue]]。したがって、AS3では、この例は "foo、x = foo、foo = x、foo1、43、x = foo ")


1
また、非プリミティブを返す場合、valueOfまたはそれらをtoString返す場合は無視されます。どちらも存在しないか、どちらもプリミティブを返さない場合は、TypeErrorがスローされます。
bcherry 2010年

1
おかげさまで、これは私が望んでいた答えの口径です。しかし、x + "x ="であってはなりません。"42x ="?そして、x + "1"; 421利回り?また、ECMAScript標準の関連部分のURLはありますか?
ブレインジャム2010年

2
実際には、「+」はヒントを使用しないため($ 11.6.1を参照)、ToPrimitivesが呼び出されます[[DefaultValue]](no-hint)。これはと同等[[DefaultValue]](number)です。
user187291 2010年

9
これは、組み込みのDateクラスには当てはまらないようです。("" + new Date(0)) === new Date(0).toString()。DateオブジェクトはtoString()、何かに追加されると常にその値を返すようです。
kpozin

7
+1&Thx!この回答について詳しく説明しているブログ投稿を見つけました。ここでリンク/共有したいと考えています。この回答に本当に役立ちました(Dmitry A. Soshnikovのコメントを含む)。
GitaarLAB 2013年

1

TLDR

型強制、または暗黙の型変換は、弱い型付けを可能にし、JavaScript全体で使用されます。ほとんどの演算子(厳密な等価演算子===andを除く!==)と値チェック演算(などif(value)...)は、それらの値の型が演算とすぐに互換性がない場合、それらに提供される値を強制します。

値を強制するために使用される正確なメカニズムは、評価される式によって異なります。質問では、加算演算子が使用されています。

加算演算子は、最初に両方のオペランドがプリミティブであることを確認しますvalueOf。この場合、メソッドの呼び出しが必要です。toStringオーバーライドされたので、この方法は、この例で呼び出されていないvalueOfオブジェクトのメソッドはx、プリミティブ値を返します。

次に、質問のオペランドの1つが文字列であるため、両方のオペランドが文字列に変換されます。このプロセスは抽象的な内部操作ToString(注:大文字)をtoString使用し、オブジェクト(またはそのプロトタイプチェーン)のメソッドとは異なります。

最後に、結果の文字列が連結されます。

細部

JavaScriptのすべての言語タイプ(つまり、Number、BigInt、String、Boolean、Symbol、およびObject)に対応するすべてのコンストラクター関数オブジェクトのプロトタイプには、およびの2つのメソッドがvalueOfありtoStringます。

の目的はvalueOf、オブジェクトに関連付けられているプリミティブ値(ある場合)を取得することです。オブジェクトに基本となるプリミティブ値がない場合、オブジェクトは単に返されます。

valueOfがプリミティブに対して呼び出された場合、プリミティブは通常の方法でオートボックス化され、基になるプリミティブ値が返されます。文字列の場合、基礎となるプリミティブ値(つまり、によって返される値valueOf)は文字列表現そのものであることに注意してください。

次のコードは、valueOfメソッドがラッパーオブジェクトから基になるプリミティブ値を返すことを示しています。また、プリミティブに対応しない変更されていないオブジェクトインスタンスが返すプリミティブ値を持たないため、単純に自分自身を返します。

console.log(typeof new Boolean(true)) // 'object'
console.log(typeof new Boolean(true).valueOf()) // 'boolean'
console.log(({}).valueOf()) // {} (no primitive value to return)

toString一方、の目的は、オブジェクトの文字列表現を返すことです。

例えば:

console.log({}.toString()) // '[object Object]'
console.log(new Number(1).toString()) // '1'

ほとんどの操作では、JavaScriptは1つ以上のオペランドを必要なタイプに変換しようとします。この動作は、JavaScriptを使いやすくするために選択されました。JavaScriptには当初例外はありませんでしたが、これもこの設計上の決定に影響を与えた可能性があります。この種の暗黙的な型変換は型強制と呼ばれ、JavaScriptのルーズ(ウィーク)型システムの基礎となっています。この動作の背後にある複雑なルールは、型キャストの複雑さを言語自体に移し、コードから除外することを目的としています。

強制プロセス中に、2つの変換モードが発生する可能性があります。

  1. オブジェクトのプリミティブへの変換(タイプ変換自体が含まれる場合があります)、および
  2. プリミティブ型のいずれかのコンストラクタ関数オブジェクトを使用して、特定の型のインスタンスへの直接変換(すなわちNumber()Boolean()String()等)

プリミティブへの変換

非プリミティブ型を操作対象のプリミティブに変換しようとすると、抽象操作ToPrimitiveがオプションの「数値」または「文字列」の「ヒント」で呼び出されます。ヒントが省略された場合、デフォルトのヒントは 'number'です(@@toPrimitiveメソッドがオーバーライドされていない場合)。ヒントが「文字列」の場合、toString最初に試行され、次にプリミティブを返さなかったvalueOf場合に試行されtoStringます。そうでなければ、逆もまた同様です。ヒントは、変換を要求する操作によって異なります。

追加演算子はヒントを提供しないため、valueOf最初に試行されます。減算演算子は「数値」のヒントを提供するため、valueOf最初に試行されます。ヒントが「文字列」である仕様で見つけることができる唯一の状況は次のとおりです。

  1. Object#toString
  2. ToPropertyKey引数をプロパティキーとして使用できる値に変換する抽象演算

直接型変換

各オペレーターには、操作を完了するための独自のルールがあります。加算演算子はまずToPrimitive、各オペランドがプリミティブであることを確認するために使用します。次に、いずれかのオペランドが文字列の場合、意図的ToStringに各オペランドに対して抽象演算を呼び出し、文字列で期待される文字列連結動作を提供します。ToPrimitiveステップの後、両方のオペランドが文字列でない場合は、算術加算が実行されます。

加算とは異なり、減算演算子にはオーバーロードされた動作がないため、toNumeric最初にを使用してプリミティブに変換した各オペランドを呼び出しToPrimitiveます。

そう:

 1  +  1   //  2                 
'1' +  1   // '11'   Both already primitives, RHS converted to string, '1' + '1',   '11'
 1  + [2]  // '12'   [2].valueOf() returns an object, so `toString` fallback is used, 1 + String([2]), '1' + '2', 12
 1  + {}   // '1[object Object]'    {}.valueOf() is not a primitive, so toString fallback used, String(1) + String({}), '1' + '[object Object]', '1[object Object]'
 2  - {}   // NaN    {}.valueOf() is not a primitive, so toString fallback used => 2 - Number('[object Object]'), NaN
+'a'       // NaN    `ToPrimitive` passed 'number' hint), Number('a'), NaN
+''        // 0      `ToPrimitive` passed 'number' hint), Number(''), 0
+'-1'      // -1     `ToPrimitive` passed 'number' hint), Number('-1'), -1
+{}        // NaN    `ToPrimitive` passed 'number' hint', `valueOf` returns an object, so falls back to `toString`, Number('[Object object]'), NaN
 1 + 'a'   // '1a'    Both are primitives, one is a string, String(1) + 'a'
 1 + {}    // '1[object Object]'    One primitive, one object, `ToPrimitive` passed no hint, meaning conversion to string will occur, one of the operands is now a string, String(1) + String({}), `1[object Object]`
[] + []    // ''     Two objects, `ToPrimitive` passed no hint, String([]) + String([]), '' (empty string)
 1 - 'a'   // NaN    Both are primitives, one is a string, `ToPrimitive` passed 'number' hint, 1-Number('a'), 1-NaN, NaN
 1 - {}    // NaN    One primitive, one is an object, `ToPrimitive` passed 'number' hint, `valueOf` returns object, so falls back to `toString`, 1-Number([object Object]), 1-NaN, NaN
[] - []    // 0      Two objects, `ToPrimitive` passed 'number' hint => `valueOf` returns array instance, so falls back to `toString`, Number('')-Number(''), 0-0, 0

Date組み込みオブジェクトは一意であり、デフォルトの@@toPrimitiveメソッドをオーバーライドする唯一の組み込みオブジェクトであり、デフォルトのヒントは「数値」ではなく「文字列」であると推定されます。これを使用する理由Dateは、プログラマーの便宜のために、インスタンスが数値ではなくデフォルトで読み取り可能な文字列に変換されるようにするためです。@@toPrimitiveを使用して、独自のオブジェクトでオーバーライドできますSymbol.toPrimitive

次のグリッドは、抽象等価演算子(==)(source)の強制型変換の結果を示しています。

ここに画像の説明を入力してください

ご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.