設定 CodeMash 2012の「ワット」で言及されたこれらの奇妙なJavaScriptビヘイビアの説明は何ですか?




javascript.com interactive tutorial (4)

CodeMash 2012「ワット」の話は、基本的にRubyとJavaScriptを使ったいくつかの奇抜な欠点を指摘しています。

私はhttp://jsfiddle.net/fe479/9/結果のJSFiddleを作成しました。

JavaScriptに固有の振る舞い(私がRubyを知らないので)は以下の通りです。

JSFiddleでは、私の結果の一部がビデオのものと一致していないことがわかりました。なぜ私はその理由がわかりません。 しかし、私はJavaScriptがそれぞれの場面で背後で働くことをどのように処理しているか知りたいと思っています。

Empty Array + Empty Array
[] + []
result:
<Empty String>

JavaScriptで配列を使用するときに+演算子が不思議です。 これは動画の結果と一致します。

Empty Array + Object
[] + {}
result:
[Object]

これは動画の結果と一致します。 何が起きてる? なぜこれがオブジェクトなのですか? +演算子は何をしますか?

Object + Empty Array
{} + []
result
[Object]

これは動画と一致しません。 ビデオは結果が0であることを示唆していますが、私は[Object]を得ます。

Object + Object
{} + {}
result:
[Object][Object]

これは動画にも一致しませんし、変数を出力すると2つのオブジェクトにどのようになるのでしょうか? たぶん私のJSFiddleは間違っています。

Array(16).join("wat" - 1)
result:
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN

wat1wat1wat1wat1 + 1の結果はwat1wat1wat1wat1ます...

私はこれが文字列から数値を減算しようとするとNaNになるという単純な動作であると思われます。


私たちは仕様を参照することがありますが、それは大変正確ですが、ほとんどの場合、次のステートメントでより分かりやすい方法で説明することもできます。

  • +演算子と-演算子はプリミティブ値でのみ動作します。 より具体的には、 + (加算)は文字列または数字で、 + (単項)および- (減算および単項)は数字のみで機能します。
  • プリミティブ値を引数とするすべてのネイティブ関数または演算子は、まずその引数を目的のプリミティブ型に変換します。 これは、どのオブジェクトでも使用できるvalueOfまたはtoStringで行われます。 そのような関数や演算子がオブジェクトに対して呼び出されたときにエラーを投げないのはそのためです。

だから私たちはそれを言うかもしれません:

  • [] + []は、 '' + ''と同じString([]) + String([])と同じです。 上で述べたように、 + (加算)も数値に対して有効ですが、JavaScriptでは配列の有効な数値表現がないため、文字列の追加が代わりに使用されます。
  • [] + {}String([]) + String({})と同じですが、 '' + '[object Object]'と同じです
  • {} + [] 。 これはもっと説明が必要です(Venteroの答えを参照)。 その場合、中括弧はオブジェクトではなく空のブロックとして扱われるため、 +[]と同じになります。 Unary +は数字だけで動作するので、実装は[]から数値を取得しようとします。 まず、arrayの場合に同じオブジェクトを返すvalueOfを試してから、最後の手段、 toString結果を数値に変換しようとします。 +Number(String([]))と同じ+Number('')と同じ+Number(String([]))として書くことができます。
  • Array(16).join("wat" - 1)減算-数値のみで動作するので、 Array(16).join(Number("wat") - 1)と同じです"wat"有効な番号に変換されます。 私たちはNaNNaN結果の算術演算を受け取ります: Array(16).join(NaN)

これは答えよりもコメントのほうが多いですが、何らかの理由であなたの質問にコメントできません。 私はあなたのJSFiddleコードを修正したいと思っていました。 しかし、私はこれをHacker Newsに掲載し、誰かが私がここにそれを再投稿することを提案しました。

JSFiddleコードの問題点は、 ({}) (かっこ内の中かっこを開く)が{}と同じではないことです(コードの先頭に中カッコを入れます)。 だからあなたがout({} + [])を打つとき、あなたは{} + []をタイプするときではないものに{}を強制します。 これはJavascript全体の「ワット」の一部です。

基本的なアイデアはシンプルなJavaScriptは、これらのフォームの両方を許可したかった:

if (u)
    v;

if (x) {
    y;
    z;
}

そうするために、オープニングブレースの2つの解釈がなされた:1.それは必要ではなく 、2.それはどこにでも現れることができる

これは間違った動きでした。 実際のコードはどこにも現れていない開きブレースを持っておらず、実際のコードは2番目のフォームではなく最初のフォームを使用すると壊れやすい傾向があります。 (私の最後の仕事で月に1度、コードの修正がうまくいかなかったときに同僚の机に電話がかかってしまいました。問題は、カーリーを追加せずに "if"に行を追加したことです私は結局、1行しか書いていないときでも、中括弧が常に必要であるという習慣を採用しました。)

幸いにも、多くの場合、eval()はJavaScriptのワイトネスを再現します。 JSFiddleコードは次のようになります。

function out(code) {
    function format(x) {
        return typeof x === "string" ?
            JSON.stringify(x) : x;
    }   
    document.writeln('&gt;&gt;&gt; ' + code);
    document.writeln(format(eval(code)));
}
document.writeln("<pre>");
out('[] + []');
out('[] + {}');
out('{} + []');
out('{} + {}');
out('Array(16).join("wat" + 1)');
out('Array(16).join("wat - 1")');
out('Array(16).join("wat" - 1) + " Batman!"');
document.writeln("</pre>");

[私がdocument.writelnを何年も前から書いたのはこれが初めてで、document.writeln()とeval()の両方を含む何かを書くのはちょっと汚いと感じています。]


あなたが見ている(見ていると思われる)結果の説明のリストです。 私が使用している参照は、 ECMA-262標準からのものです。

  1. [] + []

    加算演算子を使用する場合、左オペランドと右オペランドの両方がプリミティブに最初に変換されます( §11.6.1 )。 §9.1 、オブジェクト(この場合は配列)をプリミティブに変換すると、デフォルト値が返されます。有効なtoString()メソッドを持つオブジェクトでは、 §8.12.8 object.toString()§8.12.8 )を呼び出した結果となります。 配列の場合、 array.join()§15.4.4.2 )を呼び出すのと同じです。 空の配列を結合すると空の文字列になります。したがって、加算演算子の#7は、空の文字列である2つの空の文字列の連結を返します。

  2. [] + {}

    [] + []と同様に、両方のオペランドが最初にプリミティブに変換されます。 "Object objects"(15.2)については、これもやはりobject.toString object.toString()を呼び出した結果であり、nullでない未定義のオブジェクトは"[object Object]"§15.2.4.2 )です。

  3. {} + []

    ここでの{}はオブジェクトとして解析されるのではなく、空のブロック(少なくとも§12.1ではなく詳細については§12.1を参照してください)。 空のブロックの戻り値は空であるため、そのステートメントの結果は+[]と同じです。 unary +演算子( §11.4.6 )はToNumber(ToPrimitive(operand))返します。 既にわかっているように、 ToPrimitive([])は空の文字列であり、 ToNumber("")よれば、 ToNumber("")は0です。

  4. {} + {}

    前の例と同様に、最初の{}は空の戻り値を持つブロックとして解析されます。 ここでも、 +{}ToNumber(ToPrimitive({}))ToPrimitive({})で、 ToPrimitive({})"[object Object]"[] + {}参照)。 したがって、 +{}結果を得るには、 ToNumberを文字列"[object Object]"に適用するToNumberがあります。 §9.3.1の手順を実行する§9.3.1NaNが結果として得られます。

    文法がStringNumericLiteralの拡張としてStringを解釈できない場合、 ToNumberの結果はNaNになります。

  5. Array(16).join("wat" - 1)

    §15.4.1.1§15.4.2.2Array(16)は長さ16の新しい配列を作成します。引数の値を結合するには、 §11.6.2ステップ#5と#6が、変換する必要があることを示します両方のオペランドをToNumberを使用して数値にToNumberます。 ToNumber(1)は単純に1( ToNumber )ですが、 ToNumber("wat")§9.3.1 NaN §9.3.1§11.6.2ステップ7に§11.6.2§11.6.3

    どちらかのオペランドがNaNの場合、結果はNaNになります。

    したがって、 Array(16).joinNaNです。 §15.4.4.5( Array.prototype.join )に続いて、引数"NaN"§9.8.1 )でToStringを呼び出さなければなり§9.8.1ん:

    mNaNの場合は、String "NaN"返します。

    §15.4.4.5ステップ10に§15.4.4.5"NaN"と空の文字列の連結を15回繰り返します。これはあなたが見ている結果と同じです。 "wat" + 1代わりに"wat" + 1を引数として使用すると、加算演算子は"wat"を数値に変換するのではなく、 1を文字列に変換するので、 Array(16).join("wat1")

なぜ{} + []場合に異なる結果が出ているのか:関数の引数として使用すると、文をExpressionStatementにして{}を空のブロックとして解析できなくなります。代わりに空のオブジェクトリテラルとして解析されます。


以前共有されたものを支持する。

この動作の根底にある原因の一部は、JavaScriptの弱い型付けの性質によるものです。 たとえば、オペランドの型(int、string)と(int int)に基づいて2つの解釈が可能であるため、式1 + "2"はあいまいです。

  • ユーザーは2つの文字列を連結しようとします。結果は "12"
  • ユーザーは2つの数値、結果を追加する予定です:3

したがって、入力タイプが変化すると、出力可能性が増加する。

加算アルゴリズム

  1. オペランドをプリミティブ値に強制する

JavaScriptプリミティブはstring、number、null、undefined、booleanです(SymbolはすぐにES6に来ます)。 他の値はオブジェクト(配列、関数、オブジェクトなど)です。 オブジェクトをプリミティブ値に変換するための変換処理は、こうして記述される。

  • object.valueOf()が呼び出されたときにプリミティブ値が返された場合は、この値を返し、それ以外の場合は続行します

  • object.toString()が呼び出されたときにプリミティブ値が返された場合は、この値を返し、それ以外の場合は続行します。

  • TypeErrorを投げる

注:日付値の場合、順序はvalueOfの前にtoStringを呼び出すことです。

  1. オペランドの値が文字列の場合は、文字列の連結を行います

  2. それ以外の場合は、両方のオペランドを数値に変換してからこれらの値を加算します

JavaScriptの型のさまざまな強制値を知ることは、混乱している出力をより明確にするのに役立ちます。 下記の強制表を参照してください

+-----------------+-------------------+---------------+
| Primitive Value |   String value    | Numeric value |
+-----------------+-------------------+---------------+
| null            | “null”            | 0             |
| undefined       | “undefined”       | NaN           |
| true            | “true”            | 1             |
| false           | “false”           | 0             |
| 123             | “123”             | 123           |
| []              | “”                | 0             |
| {}              | “[object Object]” | NaN           |
+-----------------+-------------------+---------------+

また、JavaScriptの+演算子は、複数の+演算を含むケースが出力になるものを決定するので、左辺結合型であることがわかります。

したがって、1 + "2"を使用すると、文字列を含むすべての追加が常に文字列連結にデフォルト設定されるため、 "12"が得られます。

あなたはこのブログの投稿 (私がそれを書いた免責事項)でより多くの例を読むことができます。







javascript