セミコロンについて知っておくべきこと

セミコロン( ";")の自動挿入は、javascriptの最も物議を醸す機能の1つであり、その周辺には多くの誤解が蓄積されています。

一部のプログラマーは「;」を入れます 各ステートメントの最後に、一部は厳密に必要な場合のみ。 余分な「;」を追加するものもありますが、ほとんどは中間のどこかにあります 文体的な理由から。

常に「;」を入れても 各ステートメントの最後に、いくつかの構文が自明な方法で解析されます。 「;」に関する好みに関係なく、言語を専門的に使用するには、そのような解析の規則を知っておく必要があります。 以下のいくつかの簡単なルールを思い出して、プログラムがどのように解析されるかを理解し、自動挿入「;」のエキスパートになります。 JavaScriptで。



TKが許容される場合



ECMAscript仕様で指定されている正式な文法では、「;」 各演算子の末尾にあります。 do-whileステートメントは次のとおりです。

doステートメントwhile(式);

TKは、varステートメント、式ステートメント(「4 + 4;」または「f();」など)、continue、return、break、throwステートメントおよびデバッガーステートメントの最後の文法にも表示されます。

空のステートメントは「;」であり、javascriptの正しいステートメントです。 このため、「;;;」 有効なプログラムであり、3つの空のステートメントとして解析され、3回は実行されません。

少なくとも構文的には、空のステートメントが役立つ場合があります。 たとえば、無限ループの場合、「while(1);」と記述できます。セミコロンは空のステートメントとして解析され、whileステートメントが構文的に有効になります。 TKがないと、ループ条件の後に演算子が必要になるため、whileステートメントは不完全になります。

最後に、「;」 ループでは「for(Expression; Expression; Expression)Operator」の形式で使用されます。もちろん、小文字および正規表現リテラルで使用できます。

セミコロンをスキップできる場所



ECMAscript仕様の正式な文法では、「;」 上記のとおり。 ただし、この仕様では、実際の構文解析が正式な文法とどのように異なるかを記述する規則も提供しています。 ルールは、入力ストリームに挿入された想像上の「;」で記述されますが、これは単なる仕様モデルです。実際には、パーサーは擬似「;」を生成する必要はありませんが、「;」 特定の場所でのオプションの文法として(たとえば、ECMAscriptの構文解析、特にStatement、EOS、EOSnoLB、およびSnoLBルールを参照)。 仕様で「挿入」、「」と書かれている箇所は、現在のステートメントが終了することを意味します。

TKの自動挿入のルールは、 ECMA-262 [pdf]のセクション7.9で説明されています。

このセクションでは、3つのルールと2つの例外を示します。

ルールは次のとおりです。

プログラムが文法上無効なトークンに遭遇すると、(a)この場所に改行がある場合、または(b)無効なトークンが閉じ中括弧の場合、「;」が挿入されます。 ファイルの終わりに到達し、異なる解釈が不可能になると、「;」が挿入されます。 文法で「[ここにLineTerminatorがありません」」と書かれている場所に行末記号を含む「制限されたプロダクション」が表示されたら、「;」が挿入されます。

言い換えれば、これらの規則は、演算子が「;」なしで終了できると言っています。 (a)右中括弧の前、(b)プログラムの最後、(c)次のトークンを解析できない場合、さらに、改行がステートメントを無条件に完了するいくつかの場所があります。 これが実際に何を意味するかを以下で説明します。

例外:「;」 「for(Expression; Expression; Expression)Operator」および「;」という形式のループヘッダーに挿入されない 結果が空のステートメントの場合は挿入されません。

これはすべて私たちにとって何を意味しますか?

まず、「;」 行末、中括弧の前、およびプログラムの最後でのみオプションです。 さらに、「;」 次の行の最初のトークンが前のステートメントの一部として解析できる場合、行末で想定されていません。

「42; "hello!" "は有効なプログラムの例であり、" 42 \ n "hello!" "(where" \ n "改行を表す)が、" 42 "hello!" "は行の折り返しが自動挿入を引き起こすため、もはや存在しない。 「スペースはありません。 「If(x){y()}」も有効です。 ここで、「y()」は「;」で終わることができる式ステートメントですが、次のトークンは右中括弧なので、「;」 オプション、改行なしにもかかわらず。

forループと空のステートメントの両方の例外を一緒に説明できます。

  for(node = getNode();
      node.parent;
      node = node.parent); 


ループは、親のないノードが見つかるまで次の親ノードを順番に呼び出します。 これはすべてループヘッダーで行われるため、ループ本体には何も残りません。 ただし、ループ構文には演算子が必要であり、空の演算子を挿入します。 3つすべてが「;」であるという事実にもかかわらず この例では、行末に「;」があるため、3つすべてが必要です。 ループヘッダーに挿入されないか、空のステートメントを作成しません。

限定スポーン



限定されたスポーンでは、特定の場所で改行を見つけることができません。これらの場所で改行すると、プログラムは同じ方法で解析されませんが、異なる方法で解析できるためです。

文法には5つの制限されたスパンがあります。これらは、後置演算子++および-、continue、break、およびreturnステートメントです。 breakおよびcontinueステートメントには、特定のループから制御を渡すためのオプションの識別子が含まれる場合があります。 この機能を使用する場合、識別子は同じ行になければなりません。 これは有効なプログラムです。
 var c、i、l、quitchars
 quitchars = ['q'、 'Q']
 charloop:while(c = getc()){
     for(i = 0; i <quitchars.length; i ++){
         if(c == quitchars [i])break charloop
     }
     / * ...他の文字のコード... * /
 }


getc()が入力ストリームから文字を返す場合、プログラムはそれを読み取り、出力文字かどうかを確認し、出力文字である場合は、ループの制御を渡します。 マークされたbreakステートメントは、内側のループだけでなく、外側のループから抜け出すために必要です。 改行のみが異なる同じプログラムでは、同じ結果は得られません。
 var c、i、l、quitchars
 quitchars = ['q'、 'Q']
 charloop:while(c = getc()){
     for(i = 0; i <quitchars.length; i ++){
         if(c == quitchars [i])break
            チャーループ
     }
     / * ...他の文字のコード... * /
 }


この場合、charloopトークンはbreakステートメントの一部ではありません。 breakステートメントは制限されているため、この位置での改行によりステートメントが完了します。 charloopトークンはcharloop変数のように解析されますが、この場所は制御されず、breakステートメントは意図したとおりに外部ループではなく内部ループを終了します。

他の4つの境界付きスポーンの例:

 // PostfixExpression:                                            
 // LeftHandSideExpression [LineTerminatorはありません] ++
 // LeftHandSideExpression [ここにLineTerminatorはありません]-
 var i = 1;
私は
 ++;


これによりエラーがスローされ、「i ++」として解析されません。 ターミネータは、後置インクリメントまたはデクリメント演算子を分離できないため、行の先頭の「++」または「-」は前の行の一部として解析されることはありません。

私は
 ++
 j


これは間違いではなく、「i;」として解析されます。 ++ j。 " プレフィックスのインクリメントとデクリメントは限定的な世代ではないため、「++」または「-」とそれらが変更する式の間で改行が発生する場合があります。

 // ReturnStatement:return [LineTerminator here here] Expressionopt;
帰る
   {i:i、j:j}


これは、空のreturnステートメントとして解析され、その後に制御が到達しない式演算子が続きます。 ただし、計画どおりに解析されます。

 return {
   i:i、j:j}
リターン(
   {i:i、j:j})
 return {i:i
        、j:j}


returnステートメントには式の中にハイフンが含まれている場合がありますが、returnトークンと式の先頭の間にはない場合があります。 「;」を意図的に省略した場合、returnステートメントの限定生成は便利です。これは、次の行から誤って式を返すことなく空のreturnを書き込むことができるためです。

関数初期化(a){
   // aがすでに初期化されている場合、戻ります
   if(a.initialized)return
   a.initialized = true
   / * ... ...を初期化します* /
 }


continueおよびthrowステートメントは、breakおよびreturnに似ています。

 innerloopを続行// true
 
続ける
     innerloop;  //間違っている
 // ThrowStatement:throw [no LineTerminator here] Expression;
 throw //解析エラー
   new MyComplexError(a、b、c、more、args);
 // return、break、continueとは異なり、 
 //スロー後の式が必要です、 
 //したがって、上記はまったく解析されません。
新しいMyComplexError(a、b、c、more、args)をスローします。  // true
新しいMyComplexError(
     a、b、c、more、args);  //また真
 // throwとnewが同じ行にあるオプションはすべて正しいです。


インデントはECMAscriptプログラムの解析には関係なく、改行の有無が再生されます。 したがって、javascriptのソースコードプロセッサは、プログラムのセマンティクスに影響を与えることなく、先頭のスペース(文字列定数を除く!)をカットできますが、改行を任意にカットしたり、スペースやセミコロンに置き換えることはできません。 有効なプログラムのセマンティクスを変更するミニファイヤは、価値のない無効なミニファイヤであり、唯一の方法は完全で正しいパーサーを記述することです。

return、break、continue、および++および-の前の改行は、解析に影響します。 これらの製品のみが制限されているため、プログラムの読みやすさを改善するために、スペースと改行を他のどこでも自由に使用できます。 特に、論理、算術、文字列連結演算子、トリプル(または条件付き)演算子、ピリオドまたはブラケットを介したメンバーへのアクセス、関数呼び出し、forループ、switchステートメント、およびその他の制御構造は、どこでも改行を使用して記述できます。

仕様は次のとおりです。

ECMAScriptプログラマ向けの実用的なアドバイス:後置演算子「++」と「-」は、オペランドと同じ行にある必要があります。 returnまたはthrowステートメントの式は、returnまたはthrowトークンと同じ行で開始する必要があります。 breakまたはcontinueステートメントの識別子は、breakまたはcontinueトークンと同じ行になければなりません。


スポーンが制限されているプログラマの最も一般的な間違いは、特にラージオブジェクトまたは配列リテラルが返される場合、または複数行定数の場合、戻りトークンの後に行に戻り値を配置することです。 このような改行がほとんどのプログラマーにとって不自然であると思われるという事実のため、後置演算子、およびbreak、continue、throw演算子のエラーは実際にはまれです。

自動挿入「;」の最後の微妙さ 最初のルールに従い、「;」を挿入するための無効なトークンをプログラムに含める必要があります。 オプションの「;」をスキップする場合は、スキップできないオプションではないものがあることに注意してください。 このルールを使用すると、ステートメントを複数行にわたってストレッチできます。

 return obj.method( 'abc')
           .method( 'xyz')
           .method( 'pqr')
 
 「長い文字列\ n」を返します
      +「ストレッチ\ n」
      +「複数の場合」
 
 totalArea = rect_a.height * rect_a.width
           + rect_b.height * rect_b.width
           + circ.radius * circ.radius * Math.PI


ルールは、文字列の最初のトークンにのみ適用されます。 このトークンを演算子の一部として解析できる場合、演算子は続行します(解析がさらに失敗しても)。 オペレーターが最初のトークンを続行できない場合、次のトークンが開始されます(この時点で、仕様には「挿入」、「」と表示されます)。

ペアの演算子AとBの両方が個別に有効な場合にエラーが発生する可能性がありますが、最初のトークンBはAの継続としても受け入れられます。そのような場合、「;」がない場合、パーサーはBを個別の演算子として解析しません。エラー、または予期しない方法でプログラムを解析します。 したがって、「;」 スキップされた場合、プログラマは改行で区切られた演算子AとBを監視する必要があり、BはAの末尾に付加できるトークンで開始します。

JavaScriptのほとんどの演算子は識別子で始まり、残りのほとんどは「var」、「function」、「if」などのキーワードで始まります。 識別子またはキーワードで始まるこのような演算子B、および文字列定数で始まる行には、有効な演算子Aは存在しません(言語の文法からのこの証明は、読者の練習として残されています)。

 A
関数f(x){return x * x}
 
 // TKのない演算子Aの場合
 //これらの例はすべて正しく解析されます
 
 A
 f(7)
 
 A
 「文字列」.length


残念ながら、オペレーターを開始し、既に完了したトークンを継続できる5つのトークンがあります。 これらは「(」、「[」、「/」、「+」、「-」です。実際には、最初の2つが問題を引き起こします。

これは、常に改行が「;」を置き換えることができるとは限らないことを意味します。 オペレーター間。

仕様には例を示します。
                    a = b + c
                    (d + e).print()

かっこ内の式は関数呼び出しの引数として解析できるため、自動挿入「;」では変換できません。
                    a = b + c(d + e).print()

仕様では、「代入演算子が左角かっこで始まる必要がある場合、前の行に明示的にセミコロンを配置することをお勧めします。」 より厳密な代替策は、TKを行の先頭、トークンの直前に設定することです。これにより、あいまいさが生じるリスクがあります。
                    a = b + c
                    ;(d + e).print()

括弧または角括弧で始まる演算子はまれですが、発生します。

map、filter、forEachなどの「機能的な」操作は配列でより頻繁に行われるため、角括弧を使用した例はより頻繁に行われます。 多くの場合、副作用に必要なforEachを含む大規模なリテラルを作成すると便利です。
 [['January'、 'Jan']]
 、['February'、 'Feb']
 、[「3月」、「3月」]
 、['April'、 'Apr']
 、[「5月」、「5月」]
 、['June'、 'Jun']
 、[「7月」、「7月」]
 、[「8月」、「8月」]
 、[「9月」、「9月」]
 、['October'、 'Oct']
 、[「11月」、「11月」]
 、[「12月」、「12月」]
 ] .forEach(関数(a){print( "略語" + a [0] + "is" + a [1] + "。")})
 
 ['/script.js'
 、 '/ style1.css'
 、「/ style2.css」
 、「/ page1.html」
 ] .forEach(関数(uri){
   ログ(「ルックアップとキャッシュ」+ uri)
    fetch_and_cache(uri)})

代入で大規模なリテラルが使用される場合、または関数が渡される場合、ステートメントの先頭には表示されないため、最初の開き角括弧はまれですが、発生します。

最後の問題トークンはスラッシュであり、非常に直感的ではありません。 見てみましょう:
 var i、s
 s = "here is a string"
 i = 0
 /[az.BIZ/g.exec(s)

1行目から3行目で変数を開始し、4行目でrex参照リテラル "/ [az] / g"を作成します。これはazをグローバルに検索し、execメソッドを使用してこの正規表現を文字列で呼び出します。 exec()の戻り値は使用されないため、コードは特に有用ではありませんが、少なくともコンパイルされることが期待されます。 ただし、スラッシュは正規表現を開始するだけでなく、除算演算子でもあります。 つまり、行4の先頭のスラッシュは、前の行の代入演算子の継続として解析されます。 これらの行は、「iは0に等しい[az]による除算はg.exec(s)による除算に等しい」と解析されます。

実際には、正規表現演算子を開始する理由はほとんどないため、この問題はほとんど発生しません。 上記の例では、exec()の呼び出しの値は通常、関数に渡されるか、変数に割り当てられます。いずれの場合も、行はスラッシュで始まりません。 ここでも、例外としてforEachメソッドがあります。これは、exec()を呼び出して返される値に対して[ original:便利に使用 ]を便利に使用できます。

演算子「+」および「-」を単項として使用して、値を数値型に変換し、「-」の場合に符号を逆にすることができます。 行の先頭で「;」を使用した場合 それらは、対応する2項演算子、および前の演算子の継続として認識できます。 しかし、これはめったに問題になりません。最初の単項演算子はregexpよりも一般的ではないからです(また、完全に見えません)。 正規表現と同様に、プログラマーが値を数値にキャストしたい場合、何らかの方法でこの値を使用し、変数に割り当て、または関数に渡しますが、単項演算子は先頭にありません。
 var x、y、z
 x = + y;  //便利
 y = -y;  //便利
印刷(-y);  //便利
 + z;  //役に立たない

そのような場合はすべて、「;」を省略すると、セミコロンのようにブラケットで行を開始するのが安全な方法です。 演算子「+」、「-」、またはスラッシュのまれなケースについても同じアドバイス。 したがって、TKがどこでも使用されていない場合でも、前の行がどのように変化するかに関係なく、行は誤った解析から保護されます。

誤解



多くの初心者のJavascriptプログラマーは、「;」 すべての場所で、自動挿入ルール「;」を使用しない場合、言語のこのプロパティは無視できると考えています。 上記の制限された生成ルール、特にreturnステートメントのため、これは当てはまりません。 そして、彼らは限られた産卵に慣れると、改行を恐れ始め、読みやすさを改善したとしても避けます。 自動挿入「;」のルールを学習して、任意のコードを読み取り、最も明確な方法でコードを記述できるようにすることをお勧めします。

もう1つの誤解は、javascriptブラウザエンジンのバグにより、どこでもセミコロンの信頼性が高くなり、互換性が向上するということです。 これは単にそうではありません。 すべての既存のブラウザは、自動挿入「;」に関して仕様を正しく実装しており、存在する可能性のあるバグは、Webの初期の歴史の闇に長く入り込んでいます。 ブラウザの互換性を心配する理由はありません。すべてのブラウザは上記のルールを実装しています。

おわりに



セミコロンを入れるべきですか? あなたのビジネス。 単純に、選択は情報に基づいて行われるべきであり、未知の構文トラップや存在しないブラウザのバグに対する漠然とした恐怖ではありません。 これらのルールを覚えていれば、正しい選択の準備ができているので、javascriptのコードを簡単に読むことができます。

「;」を付けないことにした場合、それらを開始する演算子、および「/」、「+」、「-」で始まる演算子では、そのような演算子を記述した場合は、開き角括弧の前に置くことをお勧めします。

セミコロンに関係なく、制限された生成の規則(戻り、ブレーク、継続、スロー、および後置インクリメントおよびデクリメント演算子)を覚えておいてください。コードの利便性と読みやすさのために、他の場所で行を分割できます。

Source: https://habr.com/ru/post/J111563/


All Articles