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


107

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


3
@NinaScholz:わかりません。構文エラーはありません。私はそれを引き受けるであろうb.z = 1b.e = 1最初の(上の右結合性を与えられた実行=し、)a.x.y.z = ...実行し、失敗します。なぜb割り当てが1つの場合に合格し、他の場合に合格しないのですか
アマダン

3
@NinaScholz私たちは、プロパティyがに存在しないことに同意しa.xます。しかし、それはどちらの場合にも当てはまります。2番目のケースでは右側の割り当てが妨げられ、最初のケースでは妨げられないのはなぜですか?実行順序の違いは何ですか?(構文エラーのタイミングはランタイムエラーのタイミングとは非常に異なるため、私は構文エラーについて説明しました。)
Amadan

@Amadanコードの実行後にエラーが発生し、変数名を再度使用して値を確認する
コードマニアック

2
これは、Javascriptが割り当て操作ecma-international.org/ecma-262/5.1/#sec-11.13
Solomon Tam

2
理論的な観点からは興味深いですが、これは「予期しない動作」の「これがそのようなコードを記述しない理由」のカテゴリーに確実に該当します。
John Montgomery

回答:


152

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

ケースa.x.y

未定義のプロパティ 'y'を設定できません

ケースa.x.y.z

未定義のプロパティ 'y'を読み取れません

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

事例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)
}

コメントで、ソロモンタム、割り当て操作に関するこのECMAドキュメントを見つけました。


57

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

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

スペックを見る:

生産AssignmentExpression : LeftHandSideExpression = AssignmentExpressionは次のように評価されます。

  1. LeftHandSideExpressionを評価した結果をlrefとします。

  2. rrefを、AssignmentExpressionを評価した結果とします。

  3. rvalをとしますGetValue(rref)

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

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

PutValue投げるのはTypeError

  1. OとするToObject(base)

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

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

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

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

あなたがするとき

a.x.y = b.e = 1

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

対照的に:

a.x.y.z = b.e = 1

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


20
カンマ演算子の巧妙なトリック-そのように使用することを考えたことはありません(もちろん、デバッグ専用です)。
ecraig12345

2
s / parse / evaluate /
ベルギ

3

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

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

コードの実行に必要な手順の大まかな概要は次のとおりですref

  1. 左側を評価します。次の2つの点に注意してください。
    • 式の評価は、式の値を取得することと同じではありません。
    • アクセサプロパティを評価REF例えばするa.x.y参照返すREFベース値からなるa.x(未定義)と、参照名を(y)。
  2. 右側を評価します。
  3. 手順2で取得した結果の値を取得します。
  4. 手順1で取得した参照の値を手順3で取得した値に設定yします。つまり、undefinedのプロパティをその値に設定します。これはTypeError例外refをスローすることになっています。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.