JavaScript型強制の説明

エンジンを知る

JavaScriptで奇妙なことが起こりうる

[編集2/5/2018]:この投稿はロシア語で利用可能になりました。 Serj Bulavykの努力に拍手。

型強制は、値をある型から別の型に変換するプロセスです(文字列から数値、オブジェクトからブールなど)。プリミティブであれオブジェクトであれ、どの型も型強制の有効な対象です。思い出すと、プリミティブは次のとおりです。数値、文字列、ブール値、null、未定義+シンボル(ES6で追加)。

実際の型強制の例として、JavaScriptの比較表を見てください。これは、緩い等価==演算子がさまざまなa型とb型に対してどのように動作するかを示しています。このマトリックスは、==演算子が行う暗黙の型強制のために恐ろしく見え、これらのすべての組み合わせを覚えることはほとんど不可能です。そして、あなたはそれをする必要はありません-基本的な型強制の原則を学んでください。

この記事では、JavaScriptで型強制がどのように機能するかについて詳しく説明し、重要な知識を習得します。これにより、次の式がどのように計算されるかを自信を持って説明できます。記事の終わりまでに、回答を表示して説明します。

true + false
12 / "6"
「数字」+ 15 + 3
15 + 3 +「数値」
[1]> null
「foo」+ +「bar」
'true' == true
false == 'false'
null == ''
!! "false" == !! "true"
[‘x’] == ‘x’
[] + null + 1
[1,2,3] == [1,2,3]
{} + [] + {} + [1]
!+ [] + [] +![]
新しい日付(0)-0
新しい日付(0)+ 0

はい、このリストには開発者としてできる愚かなことがたくさんあります。ユースケースの90%では、暗黙的な型強制を回避することをお勧めします。このリストは、型強制がどのように機能するかについての知識をテストするための学習演習と考えてください。退屈している場合は、wtfjs.comで他の例を見つけることができます。

ところで、JavaScript開発者の立場については、インタビューでそのような質問に直面することがあります。だから、読み続けて

暗黙的強制と明示的強制

型の強制は、明示的および暗黙的です。

開発者がNumber(value)のような適切なコードを記述することで型を変換する意図を表現する場合、明示的な型強制(または型キャスト)と呼ばれます。

JavaScriptは型が弱い言語であるため、値は異なる型の間で自動的に変換されることもあり、暗黙的な型強制と呼ばれます。通常、次のような異なるタイプの値に演算子を適用すると発生します
1 == null、2 / ’5'、null + new Date()、またはif(value){…}のように、周囲のコンテキストによってトリガーできます。値はブール値に強制されます。

暗黙の型強制をトリガーしない演算子の1つは===で、これは厳密な等価演算子と呼ばれます。一方、緩やかな等価演算子==は、必要に応じて比較と型強制の両方を行います。

暗黙の型強制は両刃の剣です。フラストレーションや欠陥の大きな原因であるだけでなく、読みやすさを損なうことなくより少ないコードを書くことができる便利なメカニズムです。

3種類の変換

最初に知っておくべきルールは、JavaScriptには3種類の変換しかありません。

  • ひもに
  • ブール値に
  • 番号に

第二に、プリミティブとオブジェクトの変換ロジックの動作は異なりますが、プリミティブとオブジェクトの両方を変換できるのはこれら3つの方法のみです。

最初にプリミティブから始めましょう。

文字列変換

値を明示的に文字列に変換するには、String()関数を適用します。暗黙の強制は、オペランドが文字列の場合、バイナリ+演算子によってトリガーされます。

String(123)//明示的
123 + '' //暗黙的

すべてのプリミティブ値は、予想どおりに自然に文字列に変換されます。

String(123)// '123'
String(-12.3)// '-12.3'
String(null)// 'null'
String(undefined)// 'undefined'
String(true)// 'true'
String(false)// 'false'

シンボル変換は、明示的にのみ変換できますが、暗黙的には変換できないため、少し注意が必要です。シンボル強制ルールの詳細をご覧ください。

String(Symbol( 'my symbol'))// 'Symbol(my symbol)'
'' + Symbol( 'my symbol')// TypeErrorがスローされます

ブール変換

値を明示的にブール値に変換するには、Boolean()関数を適用します。
暗黙的な変換は論理コンテキストで発生するか、論理演算子(|| &&!)によってトリガーされます。

Boolean(2)//明示的
if(2){...} //論理コンテキストにより暗黙的
!! 2 //論理演算子により暗黙的
2 || 'hello' //論理演算子により暗黙的

注:||などの論理演算子および&&は内部的にブール値変換を行いますが、実際にはブール値ではない場合でも元のオペランドの値を返します。

// trueを返す代わりに123を返します
//「hello」と123はまだ式を計算するために内部的にブール値に強制されます
let x = 'hello' && 123; // x === 123

ブール変換の結果がtrueまたはfalseの2つしかないとすぐに、偽の値のリストを覚えやすくなります。

Boolean( '')// false
Boolean(0)// false
ブール値(-0)// false
Boolean(NaN)// false
Boolean(null)// false
Boolean(undefined)// false
Boolean(false)// false

リストにない値はすべて、オブジェクト、関数、配列、日付、ユーザー定義型などを含むtrueに変換されます。シンボルは真実の値です。空のオブジェクトと配列も真の値です:

Boolean({})// true
Boolean([])// true
Boolean(Symbol())// true
!! Symbol()// true
Boolean(function(){})// true

数値変換

明示的な変換には、Boolean()およびString()で行ったのと同じように、Number()関数を適用するだけです。

暗黙的な変換は、多くの場合にトリガーされるため、注意が必要です。

  • 比較演算子(>、<、<=、> =)
  • ビットごとの演算子(|&^〜)
  • 算術演算子(-+ * /%)。オペランドが文字列の場合、binary +は数値変換をトリガーしないことに注意してください。
  • 単項+演算子
  • 緩やかな等価演算子==(含む!=)。
    両方のオペランドが文字列の場合、==は数値変換をトリガーしないことに注意してください。
Number( '123')//明示的
+ '123' //暗黙的
123!= '456' //暗黙的
4> '5' //暗黙的
5 / null //暗黙的
真| 0 //暗黙的

プリミティブ値が数値に変換される方法は次のとおりです。

Number(null)// 0
Number(undefined)// NaN
Number(true)// 1
Number(false)// 0
Number( "12")// 12
Number( "-12.34")// -12.34
Number( "\ n")// 0
Number( "12s")// NaN
Number(123)// 123

文字列を数値に変換するとき、エンジンは最初に先頭と末尾の空白、\ n、\ t文字をトリミングし、トリミングされた文字列が有効な数値を表していない場合はNaNを返します。文字列が空の場合、0を返します。

nullとundefinedは異なる方法で処理されます。nullは0になり、undefinedはNaNになります。

シンボルは、明示的にも暗黙的にも数値に変換できません。さらに、未定義の場合に発生するように、静かにNaNに変換する代わりに、TypeErrorがスローされます。 MDNのシンボル変換ルールの詳細を参照してください。

Number(Symbol( 'my symbol'))// TypeErrorがスローされます
+ Symbol( '123')// TypeErrorがスローされます

覚えておくべき2つの特別なルールがあります。

  1. ==をnullまたは未定義に適用すると、数値変換は行われません。 nullはnullまたはundefinedのみに等しく、他のものとは等しくありません。
null == 0 // false、nullは0に変換されません
null == null // true
undefined == undefined // true
null == undefined // true

2. NaNは、それ自体でも何にも等しくありません。

if(value!== value){console.log( "ここでNaNを扱っています")}

オブジェクトの型強制

これまで、プリミティブ値の型強制について見てきました。それはあまりエキサイティングではありません。

オブジェクトに関しては、エンジンが[1] + [2,3]のような式に遭遇すると、まずオブジェクトをプリミティブ値に変換する必要があり、それが最終的な型に変換されます。また、変換には、数値、文字列、ブールの3種類しかありません。

最も単純なケースはブール変換です。非プリミティブ値は常に
オブジェクトまたは配列が空かどうかに関係なく、trueに強制されます。

オブジェクトは、数値変換と文字列変換の両方を担当する内部[[ToPrimitive]]メソッドを介してプリミティブに変換されます。

[[ToPrimitive]]メソッドの擬似実装は次のとおりです。

[[ToPrimitive]]は、入力値と変換の優先タイプであるNumberまたはStringとともに渡されます。 preferredTypeはオプションです。

数値変換と文字列変換の両方で、入力オブジェクトの2つのメソッドvalueOfとtoStringを使用します。どちらのメソッドもObject.prototypeで宣言されているため、Date、Arrayなどの派生型で使用できます。

一般的に、アルゴリズムは次のとおりです。

  1. 入力が既にプリミティブである場合、何もせずにそれを返します。

2. input.toString()を呼び出します。結果がプリミティブな場合は、それを返します。

3. input.valueOf()を呼び出します。結果がプリミティブな場合は、それを返します。

4. input.toString()もinput.valueOf()もプリミティブを生成しない場合、TypeErrorをスローします。

数値変換は、toString(2)へのフォールバックを使用して、最初にvalueOf(3)を呼び出します。文字列変換は逆の処理を行います。toString(2)の後にvalueOf(3)が続きます。

ほとんどの組み込み型はvalueOfを持たないか、このオブジェクト自体を返すvalueOfを持たないため、プリミティブではないため無視されます。そのため、数値と文字列の変換が同じように機能する可能性があります。両方ともtoString()を呼び出します。

preferredTypeパラメーターを使用して、さまざまな演算子で数値または文字列の変換をトリガーできます。ただし、2つの例外があります:緩やかな等価==およびバイナリ+演算子は、デフォルトの変換モードをトリガーします(preferredTypeが指定されていないか、デフォルトに等しい)。この場合、文字列変換を行う日付を除き、ほとんどの組み込み型はデフォルトとして数値変換を想定しています。

日付変換動作の例を次に示します。

デフォルトのtoString()およびvalueOf()メソッドをオーバーライドして、オブジェクトからプリミティブへの変換ロジックにフックできます。

obj + ’’が文字列として「101」を返すことに注意してください。 +演算子はデフォルトの変換モードをトリガーします。前述のように、Objectはデフォルトとして数値変換を想定しているため、toString()の代わりにvalueOf()メソッドを最初に使用します。

ES6 Symbol.toPrimitiveメソッド

ES5では、toStringメソッドとvalueOfメソッドをオーバーライドすることで、オブジェクトからプリミティブへの変換ロジックをフックできます。

ES6では、オブジェクトに[Symbol.toPrimtive]メソッドを実装することで、さらに進んでinternal [[ToPrimitive]]ルーチンを完全に置き換えることができます。

理論を武器に、例に戻りましょう。

true + false // 1
12 / "6" // 2
"number" + 15 + 3 // 'number153'
15 + 3 + "number" // '18number'
[1]> null // true
"foo" + + "bar" // 'fooNaN'
'true' == true // false
false == 'false' // false
null == '' // false
!! "false" == !! "true" // true
['x'] == 'x' // true
[] + null + 1 // 'null1'
[1,2,3] == [1,2,3] // false
{} + [] + {} + [1] // '0 [オブジェクトオブジェクト] 1'
!+ [] + [] +![] // 'truefalse'
new Date(0)-0 // 0
new Date(0)+ 0 // '1970年1月1日木曜日02:00:00(EET)0'

以下に、各式の説明があります。

バイナリ+演算子は、trueおよびfalseの数値変換をトリガーします

true + false
==> 1 + 0
==> 1

算術除算演算子/文字列 '6'の数値変換をトリガーします。

12 / '6'
==> 12/6
== >> 2

演算子+には左から右への結合性があるため、式 "number" + 15が最初に実行されます。 1つのオペランドは文字列であるため、+演算子は数値15の文字列変換をトリガーします。2番目のステップでは、式 "number15" + 3が同様に評価されます。

「数字」+ 15 + 3
==> "number15" + 3
==> "number153"

式15 + 3が最初に評価されます。両方のオペランドが数値であるため、強制の必要はまったくありません。 2番目のステップでは、式18 + 'number'が評価され、1つのオペランドがストリングであるため、ストリング変換がトリガーされます。

15 + 3 +「数値」
==> 18 + "number"
==> "18number"

比較演算子>は、[1]およびnullの数値変換をトリガーします。

[1]> null
==> '1'> 0
==> 1> 0
==> true

単項+演算子は、二項+演算子よりも優先順位が高くなります。したがって、+ 'bar'式が最初に評価されます。単項プラスは、文字列「bar」の数値変換をトリガーします。文字列は有効な数値を表していないため、結果はNaNです。 2番目のステップで、式 'foo' + NaNが評価されます。

「foo」+ +「bar」
==> "foo" +(+ "bar")
==> "foo" + NaN
==> "fooNaN"

==演算子は数値変換をトリガーし、文字列 'true'はNaNに変換され、ブール値trueは1に変換されます。

'true' == true
==> NaN == 1
==> false
false == 'false'
==> 0 == NaN
==> false

==通常、数値変換をトリガーしますが、nullの場合はそうではありません。 nullはnullまたはundefinedのみに等しく、他の何にも等しくありません。

null == ''
==> false

!!演算子は空でない文字列であるため、「true」と「false」の両方の文字列をブール値のtrueに変換します。次に、==は強制なしで2つのブール値trueの等価性をチェックします。

!! "false" == !! "true"
==> true == true
==> true

==演算子は、配列の数値変換をトリガーします。配列のvalueOf()メソッドは配列自体を返しますが、プリミティブではないため無視されます。配列のtoString()は['x']を 'x'文字列に変換します。

['x'] == 'x'
==> 'x' == 'x'
==> true

+演算子は、[]の数値変換をトリガーします。配列のvalueOf()メソッドは、配列自体を返すため無視されます。これは非プリミティブです。配列のtoStringは空の文字列を返します。

2番目のステップでは、式 '' + null + 1が評価されます。

[] + null + 1
==> '' + null + 1
==> 'null' + 1
==> 'null1'

論理|| and &&演算子は、オペランドをブール値に強制しますが、元のオペランド(ブール値ではない)を返します。 0は偽であり、「0」は空ではない文字列であるため、真実です。 {}空のオブジェクトも真実です。

0 || "0" && {}
==>(0 || "0")&& {}
==>(false || true)&& true //内部的に
==> "0" && {}
==> true && true //内部的に
==> {}

両方のオペランドが同じ型であるため、強制は必要ありません。 ==はオブジェクトの同一性をチェックし(オブジェクトの等価性はチェックしない)、2つの配列は2つの異なるインスタンスであるため、結果はfalseです。

[1,2,3] == [1,2,3]
==> false

すべてのオペランドは非プリミティブ値であるため、+は左端の数値変換のトリガーから始まります。 ObjectとArrayの両方のvalueOfメソッドはオブジェクト自体を返すため、無視されます。 toString()はフォールバックとして使用されます。ここでの秘isは、最初の{}はオブジェクトリテラルとしてではなく、ブロック宣言ステートメントとして見なされるため、無視されることです。評価は次の+ []式で始まり、toString()メソッドを介して空の文字列に変換され、その後0に変換されます。

{} + [] + {} + [1]
==> + [] + {} + [1]
==> 0 + {} + [1]
==> 0 + '[オブジェクトオブジェクト]' + [1]
==> '0 [オブジェクトオブジェクト]' + [1]
==> '0 [オブジェクトオブジェクト]' + '1'
==> '0 [オブジェクトオブジェクト] 1'

これは、演算子の優先順位に従って、順を追って説明されています。

!+ [] + [] +![]
==>(!+ [])+ [] +(![])
==>!0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'

-演算子は、日付の数値変換をトリガーします。 Date.valueOf()は、Unixエポックからのミリ秒数を返します。

新しい日付(0)-0
==> 0-0
==> 0

+演算子はデフォルトの変換をトリガーします。日付はデフォルトとして文字列変換を想定しているため、valueOf()ではなくtoString()メソッドが使用されます。

新しい日付(0)+ 0
==> '1970年1月1日木曜日02:00:00 GMT + 0200(EET)' + 0
==> '1970年1月1日木曜日02:00:00 GMT + 0200(EET)0'

資源

Nicholas C. Zakasの著書「Understanding ES6」をお勧めします。 ES6の優れた学習リソースであり、高レベルではなく、内部を掘り下げません。

そして、ここにES5のみに関する良い本があります-SpeakingJSはAxel Rauschmayerによって書かれました。

(ロシア語)СовременныйучебникJavascript — https://learn.javascript.ru/。特に、型強制に関するこれらの2つのページ。

JavaScript比較表— https://dorey.github.io/JavaScript-Equality-Table/

wtfjs —嫌いなものがたくさんあるにもかかわらず、私たちが愛する言語についての小さなコードのブログ— https://wtfjs.com/