web-dev-qa-db-ja.com

JavaScriptのこれらのスニペットは、両方にエラーが発生しても、なぜ異なる動作をするのですか?

var a = {}
var b = {}

try{
  a.x.y = b.e = 1 // Uncaught TypeError: Cannot set property 'y' of undefined
} catch(err) {
  console.error(err);
}
console.log(b.e) // 1
var a = {}
var b = {}

try {
  a.x.y.z = b.e = 1 // Uncaught TypeError: Cannot read property 'y' of undefined
} catch(err) {
  console.error(err);
}

console.log(b.e) // undefined
107
Kevin Askin

実際、エラーメッセージを適切に読んだ場合、ケース1とケース2は異なるエラーをスローします。

ケースa.x.y

未定義のプロパティ「y」をsetできません

ケースa.x.y.z

未定義のプロパティ「y」をreadできません

簡単な英語で段階的に実行して説明するのが最善だと思います。

ケース1

// 1. Declare variable `a`
// 2. Define variable `a` as {}
var a = {}

// 1. Declare variable `b`
// 2. Define variable `b` as {}
var b = {}

try {

  /**
   *  1. Read `a`, gets {}
   *  2. Read `a.x`, gets undefined
   *  3. Read `b`, gets {}
   *  4. Set `b.z` to 1, returns 1
   *  5. Set `a.x.y` to return value of `b.z = 1`
   *  6. Throws "Cannot **set** property 'y' of undefined"
   */
  a.x.y = b.z = 1
  
} catch(e){
  console.error(e.message)
} finally {
  console.log(b.z)
}

ケース2

// 1. Declare variable `a`
// 2. Define variable `a` as {}
var a = {}

// 1. Declare variable `b`
// 2. Define variable `b` as {}
var b = {}

try {

  /**
   *  1. Read `a`, gets {}
   *  2. Read `a.x`, gets undefined
   *  3. Read `a.x.y`, throws "Cannot **read** property 'y' of undefined".
   */
  a.x.y.z = b.z = 1
  
} catch(e){
  console.error(e.message)
} finally {
  console.log(b.z)
}

コメントでは、 Solomon Tam found このECMAの割り当て操作に関するドキュメント

151
Yong Quan

ブラケット表記内のカンマ演算子を利用して、次の場合に実行される部分を確認すると、操作の順序が明確になります。

_var a = {}
var b = {}

try{
 // Uncaught TypeError: Cannot set property 'y' of undefined
  a
    [console.log('x'), 'x']
    [console.log('y'), 'y']
    = (console.log('right hand side'), b.e = 1);
} catch(err) {
  console.error(err);
}
console.log(b.e) // 1_
_var a = {}
var b = {}

try {
  // Uncaught TypeError: Cannot read property 'y' of undefined
  a
    [console.log('x'), 'x']
    [console.log('y'), 'y']
    [console.log('z'), 'z']
    = (console.log('right hand side'), b.e = 1);
} catch(err) {
  console.error(err);
}

console.log(b.e) // undefined_

spec を見てください:

実動_AssignmentExpression : LeftHandSideExpression = AssignmentExpression_は次のように評価されます:

  1. LrefをLeftHandSideExpressionの評価結果とします。

  2. RrefをAssignmentExpressionの評価結果とします。

  3. RvalをGetValue(rref)とします。

  4. 次の場合にSyntaxError例外をスローします(無関係)

  5. PutValue(lref, rval)を呼び出します。

PutValueは、TypeErrorをスローするものです。

  1. OをToObject(base)とする。

  2. 引数PでOの_[[CanPut]]_内部メソッドを呼び出した結果がfalseの場合、

    a。 Throwがtrueの場合、TypeError例外をスローします。

undefinedのプロパティには何も割り当てることができません。undefinedの_[[CanPut]]_内部メソッドは常にfalseを返します。

つまり、インタープリターは左側を解析し、右側を解析します。左側のプロパティを割り当てることができない場合、 then はエラーをスローしますに。

するとき

_a.x.y = b.e = 1
_

PutValueが呼び出されるまで、左側はが正常に解析されましたです。 _.x_プロパティがundefinedと評価されるという事実は、右側が解析されるまで考慮されません。インタプリタは、「未定義のプロパティ「y」に値を割り当てる」と見なし、undefinedのプロパティに割り当てると、PutValue内でのみスローされます。

対照的に:

_a.x.y.z = b.e = 1
_

インタプリタは、最初に_a.x.y_を値に解決する必要があるため、zプロパティに割り当てようとするポイントに到達することはありません。 _a.x.y_が値(undefinedにさえ)に解決された場合、問題ありません-上記のようにPutValue内でエラーがスローされます。ただし、 accessing _a.x.y_は、yでプロパティundefinedにアクセスできないため、エラーをスローします。

57

次のコードを検討してください。

var a = {};
a.x.y = console.log("evaluating right hand side"), 1;

コードを実行するために必要な手順の大まかな概要は次のとおりです。 参照

  1. 左側を評価します。留意すべき2つのこと:
    • 式の評価は、式の値を取得することとは異なります。
    • プロパティアクセサーの評価 参照 例えばa.x.yは参照を返します 参照 ベース値a.x(未定義)および参照名(y)。
  2. 右側を評価します。
  3. 手順2で取得した結果の値を取得します。
  4. 手順1で取得した参照の値を手順3で取得した値に設定します。つまり、undefinedのproperty yを値に設定します。これは、TypeError例外をスローすることになっています 参照
3
Salman A