Tại sao các đoạn mã JavaScript này hoạt động khác nhau mặc dù cả hai đều gặp lỗi?


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: Tôi không hiểu. Không có lỗi cú pháp; vì vậy tôi sẽ giả định điều đó b.z = 1b.e = 1thực hiện trước (đã cho tính liên kết phải trên =), sau đó a.x.y.z = ...thực thi và không thành công; tại sao bchuyển nhượng được chuyển trong một trường hợp mà không phải trong trường hợp khác?
Amadan

3
@NinaScholz Chúng tôi đồng ý rằng tài sản ykhông tồn tại trên a.x; nhưng điều đó đúng trong cả hai trường hợp. Tại sao nó ngăn cản sự phân công bên phải trong trường hợp thứ hai mà không phải là trường hợp đầu tiên? Thứ tự thực hiện có gì khác nhau? (Tôi đã đề cập đến Lỗi cú pháp vì thời gian xảy ra lỗi cú pháp rất khác với lỗi thời gian chạy.)
Amadan

@Amadan sau khi chạy mã, bạn sẽ gặp lỗi và hơn là sử dụng hơn là gõ lại tên biến để xem giá trị
Mã Maniac

2
Tìm thấy này mô tả cách Javascript tiến hành hoạt động chuyển nhượng ecma-international.org/ecma-262/5.1/#sec-11.13
Solomon Tam

2
Nó thú vị từ góc độ lý thuyết, nhưng điều này chắc chắn thuộc loại hành vi không mong đợi "đây là lý do tại sao bạn không viết mã như thế".
John Montgomery

Câu trả lời:


152

Trên thực tế, nếu bạn đọc đúng thông báo lỗi, trường hợp 1 và trường hợp 2 sẽ phát ra các lỗi khác nhau.

Trường hợp a.x.y:

Không thể đặt thuộc tính 'y' của không xác định

Trường hợp a.x.y.z:

Không thể đọc thuộc tính 'y' của không xác định

Tôi đoán tốt nhất nên mô tả nó bằng cách thực hiện từng bước bằng tiếng Anh dễ hiểu.

Trường hợp 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)
}

Trường hợp 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)
}

Trong phần nhận xét, Solomon Tam đã tìm thấy tài liệu ECMA này về thao tác chuyển nhượng .


57

Thứ tự của các hoạt động rõ ràng hơn khi bạn khai thác toán tử dấu phẩy bên trong ký hiệu dấu ngoặc để xem phần nào được thực thi khi:

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

Nhìn vào thông số kỹ thuật :

Việc sản xuất AssignmentExpression : LeftHandSideExpression = AssignmentExpressionđược đánh giá như sau:

  1. Gọi lref là kết quả đánh giá LeftHandSideExpression.

  2. Gọi rref là kết quả đánh giá AssignmentExpression.

  3. Hãy để rval là GetValue(rref).

  4. Ném một ngoại lệ SyntaxError nếu ... (không liên quan)

  5. Gọi điện PutValue(lref, rval).

PutValuelà những gì ném TypeError:

  1. Hãy để O được ToObject(base).

  2. Nếu kết quả của việc gọi [[CanPut]]phương thức bên trong của O với đối số P là sai, thì

    a. Nếu Throw là true, thì hãy ném một ngoại lệ TypeError.

Không có gì có thể được gán cho thuộc tính của undefined- [[CanPut]]phương thức nội bộ của undefinedsẽ luôn trả về false.

Nói cách khác: trình thông dịch phân tích cú pháp phía bên trái, sau đó phân tích cú pháp phía bên phải, sau đó đưa ra lỗi nếu không thể gán thuộc tính ở phía bên trái.

Khi bạn làm

a.x.y = b.e = 1

Phía bên tay trái được phân tích cú pháp thành công cho đến khi PutValueđược gọi; thực tế mà thuộc .xtính đánh giá undefinedsẽ không được xem xét cho đến khi phần bên phải được phân tích cú pháp. Trình thông dịch coi nó là "Gán một số giá trị cho thuộc tính" y "của undefined" và gán cho thuộc tính undefinedchỉ ném bên trong PutValue.

Ngược lại:

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

Trình thông dịch không bao giờ đạt đến điểm mà nó cố gắng gán cho thuộc ztính, vì trước tiên nó phải phân giải a.x.ythành một giá trị. Nếu a.x.yđược giải quyết thành một giá trị (thậm chí thành undefined), nó sẽ không sao - một lỗi sẽ được đưa vào bên trong PutValuenhư trên. Nhưng việc truy cập a.x.y sẽ gặp lỗi, vì ykhông thể truy cập thuộc tính vào undefined.


20
Thủ thuật toán tử dấu phẩy hay - bạn chưa bao giờ nghĩ sẽ sử dụng nó theo cách đó (tất nhiên chỉ để gỡ lỗi)!
ecraig12345

2
s / parse /
eval

3

Hãy xem xét đoạn mã sau:

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

Sơ lược về các bước cần thiết để thực thi mã như sau : ref :

  1. Đánh giá mặt trái. Hai điều cần ghi nhớ:
    • Đánh giá một biểu thức không giống như nhận giá trị của biểu thức.
    • Đánh giá một ref của trình truy cập thuộc tính, ví dụ a.x.ytrả về một ref tham chiếu bao gồm giá trị cơ sở a.x(không xác định) và tên được tham chiếu ( y).
  2. Đánh giá bên tay phải.
  3. Nhận giá trị của kết quả thu được ở bước 2.
  4. Đặt giá trị của tham chiếu thu được ở bước 1 thành giá trị thu được ở bước 3, tức là đặt ythuộc tính không xác định thành giá trị. Điều này được cho là để ném một ref ngoại lệ TypeError .
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.