Tại sao 0 [0] lại hợp lệ về mặt cú pháp?


119

Tại sao dòng này hợp lệ trong javascript?

var a = 0[0];

Sau đó, aundefined.


4
như true[0]hoặc ""[0]
Hacketo

24
@CodeAngry Công bằng mà nói, JavaScript được sinh ra trong HTML và chính HTML đã khởi đầu cho toàn bộ việc "ném bất cứ thứ gì bạn thích vào tôi và tôi sẽ cố gắng hiểu điều đó".
Niet the Dark Absol

8
@NiettheDarkAbsol Công bằng mà nói, bạn chỉ sai vì cú pháp ý nghĩa (nhưng không quá nhiều). Đây chỉ là lấy thuộc tính có tên "0"của một new Number(0)đối tượng.
marendre

đó là một giả định sai rằng a sẽ luôn là không xác định. Hoàn toàn có thể có 0[0]giá trị trả về
Rune FS

@meandre 0["toString"]Thật tuyệt, cảm ơn bạn đã chỉ ra.
Jonathan

Câu trả lời:


169

Khi bạn làm như vậy 0[0], trình thông dịch JS sẽ chuyển đối tượng đầu tiên 0thành một Numberđối tượng và sau đó cố gắng truy cập vào thuộc [0]tính của đối tượng đó undefined.

Không có lỗi cú pháp vì cú pháp truy cập 0[0]thuộc tính được ngữ pháp ngôn ngữ cho phép trong ngữ cảnh này. Cấu trúc này (sử dụng các thuật ngữ trong ngữ pháp Javascript) là NumericLiteral[NumericLiteral].

Phần có liên quan của ngữ pháp ngôn ngữ từ phần A.3 của thông số kỹ thuật ECMAScript ES5 là:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

MemberExpression :
    PrimaryExpression
    FunctionExpression
    MemberExpression [ Expression ]
    MemberExpression . IdentifierName
    new MemberExpression Arguments    

Vì vậy, người ta có thể theo dõi máy cắt thông qua quá trình này:

MemberExpression [ Expression ]
PrimaryExpression [ Expression ]
Literal [ Expression ]
NumericLiteral [ Expression ]

Và, tương tự, Expressioncuối cùng cũng có thể NumericLiteralnhư vậy sau khi tuân theo ngữ pháp, chúng tôi thấy rằng điều này được phép:

NumericLiteral [ NumericLiteral ]

Có nghĩa là đó 0[0]là một phần được phép của ngữ pháp và do đó không có Lỗi cú pháp.


Sau đó, tại thời điểm chạy, bạn được phép đọc một thuộc tính không tồn tại (nó sẽ chỉ được đọc là undefined) miễn là nguồn bạn đang đọc là một đối tượng hoặc có một chuyển đổi ngầm định thành một đối tượng. Và, một ký tự số thực sự có một chuyển đổi ngầm thành một đối tượng (một đối tượng Số).

Đây là một trong những tính năng thường không được biết đến của Javascript. Các loại Number, BooleanStringtrong Javascript thường được lưu trữ nội bộ như nguyên thủy (không phải đối tượng toàn diện). Đây là một biểu diễn lưu trữ nhỏ gọn, bất biến (có thể được thực hiện theo cách này để có hiệu quả triển khai). Tuy nhiên, Javascript muốn bạn có thể coi những nguyên thủy này giống như các đối tượng với các thuộc tính và phương thức. Vì vậy, nếu bạn cố gắng truy cập một thuộc tính hoặc phương thức không được hỗ trợ trực tiếp trên nguyên thủy, thì Javascript sẽ tạm thời ép buộc nguyên thủy thành một loại đối tượng thích hợp với giá trị được đặt thành giá trị của nguyên thủy.

Khi bạn sử dụng cú pháp giống đối tượng trên một nguyên thủy chẳng hạn 0[0], trình thông dịch nhận ra đây là một quyền truy cập thuộc tính trên một nguyên thủy. Phản ứng của nó đối với điều này là lấy 0nguyên mẫu số đầu tiên và ép nó thành một Numberđối tượng toàn diện mà sau đó nó có thể truy cập vào thuộc [0]tính. Trong trường hợp cụ thể này, thuộc [0]tính trên đối tượng Number undefinedlà lý do tại sao đó là giá trị bạn nhận được 0[0].

Đây là một bài viết về tự động chuyển đổi một nguyên thủy thành một đối tượng cho mục đích xử lý các thuộc tính:

Cuộc sống bí mật của những người nguyên thủy Javascript


Dưới đây là các phần có liên quan của thông số kỹ thuật ECMAScript 5.1:

9.10 CheckObjectCoercible

Ném TypeError nếu giá trị là undefinedhoặc null, nếu không trả về true.

nhập mô tả hình ảnh ở đây

11.2.1 Người tiếp cận tài sản

  1. Đặt baseReference là kết quả đánh giá MemberExpression.
  2. Đặt baseValue là GetValue (baseReference).
  3. Đặt propertyNameReference là kết quả của việc đánh giá Biểu thức.
  4. Đặt propertyNameValue là GetValue (propertyNameReference).
  5. Gọi CheckObjectCoercible (baseValue).
  6. Đặt propertyNameString là ToString (propertyNameValue).
  7. Nếu sản xuất cú pháp đang được đánh giá được chứa trong mã chế độ nghiêm ngặt, hãy đặt nghiêm ngặt là đúng, nếu không đặt nghiêm ngặt là sai.
  8. Trả về giá trị kiểu Tham chiếu có giá trị cơ sở là baseValue và có tên được tham chiếu là propertyNameString và có cờ chế độ nghiêm ngặt là nghiêm ngặt.

Một phần hữu ích cho câu hỏi này là bước # 5 ở trên.

8.7.1 GetValue (V)

Điều này mô tả cách khi giá trị đang được truy cập là một tham chiếu thuộc tính, nó sẽ gọi ToObject(base)để lấy phiên bản đối tượng của bất kỳ nguyên thủy nào.

9,9 Đối tượng

Này mô tả cách Boolean, NumberStringnguyên thủy được chuyển đổi thành một hình thức đối tượng với [[PrimitiveValue]] tài sản nội bộ cho phù hợp.


Là một thử nghiệm thú vị, nếu mã như thế này:

var x = null;
var a = x[0];

Nó vẫn sẽ không ném ra SyntaxError tại thời điểm phân tích cú pháp vì đây là cú pháp hợp pháp về mặt kỹ thuật, nhưng nó sẽ ném ra một TypeError trong thời gian chạy khi bạn chạy mã bởi vì khi logic của Bộ truy cập thuộc tính ở trên được áp dụng cho giá trị của x, nó sẽ gọi CheckObjectCoercible(x)hoặc gọi ToObject(x)cái nào cả hai sẽ ném ra một TypeError nếu xnullhoặc undefined.


0[1,2]cũng hợp lệ, nó có nghĩa là gì? (Tôi cập nhật câu hỏi)
Michael M.

Và nó không gây ra lỗi cú pháp, bởi vì việc truy cập thuộc tính trên bất kỳ thứ gì không nullhoặc undefinedhoàn toàn ổn, ngay cả khi những thuộc tính đó không tồn tại.
user4642212

6
@Michael không cần cập nhật. Đó là một nhà điều hành dấu phẩy vì vậy nó chỉ0[2]
Amit Joki

1
Comma điều hành: đánh giá cả 1 và 2 trong 1,2nhưng lợi nhuận 2.
user4642212

2
Đó là một câu trả lời tuyệt vời cho sắc thái của một câu hỏi.
tbh__

20

Giống như hầu hết các ngôn ngữ lập trình, JS sử dụng ngữ pháp để phân tích cú pháp mã của bạn và chuyển đổi nó sang dạng thực thi. Nếu không có quy tắc nào trong ngữ pháp có thể được áp dụng cho một đoạn mã cụ thể, nó sẽ tạo ra một Lỗi cú pháp. Nếu không, mã được coi là hợp lệ, bất kể nó có hợp lý hay không.

Các phần có liên quan của ngữ pháp JS

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

0[0]tuân theo các quy tắc này, nó được coi là một biểu thức hợp lệ . Nó có đúng hay không (ví dụ như không gặp lỗi trong thời gian chạy) là một câu chuyện khác, nhưng đúng là như vậy. Đây là cách JS đánh giá các biểu thức như someLiteral[someExpression]:

  1. đánh giá someExpression(có thể phức tạp tùy ý)
  2. chuyển đổi ký tự thành một loại đối tượng tương ứng (ký tự số => Number, chuỗi => Stringv.v.)
  3. gọi get propertyhoạt động trên kết quả (2) với kết quả tên thuộc tính (1)
  4. loại bỏ kết quả (2)

Vì vậy, 0[0]được hiểu là

index = 0
temp = Number(0)
result = getproperty(temp, index) // it's undefined, but JS doesn't care
delete temp
return result

Dưới đây là một ví dụ về một biểu thức hợp lệ , nhưng không chính xác :

null[0]

Nó được phân tích cú pháp tốt, nhưng tại thời gian chạy, trình thông dịch không thành công ở bước 2 (vì nullkhông thể chuyển đổi thành một đối tượng) và gây ra lỗi thời gian chạy.


1
Còn nhiều điều hơn thế nữa. var x = null; var a = x[0];không tạo ra lỗi cú pháp, nhưng không tạo ra lỗi TypeError trong thời gian chạy.
jfriend00

@ jfriend00: đó không phải là tất cả câu hỏi, nhưng đã thêm vào.
georg

kết quả không phải là không xác định. Có thể 0[0]trả về một giá trị thay vì không xác định
Rune FS

9

Có những tình huống mà bạn có thể chỉ số hợp lệ một số trong Javascript:

-> 0['toString']
function toString() { [native code] }

Mặc dù không rõ ràng ngay lập tức tại sao bạn muốn làm điều này, nhưng đăng ký trong Javascript tương đương với việc sử dụng ký hiệu chấm (mặc dù ký hiệu chấm hạn chế bạn sử dụng số nhận dạng làm khóa).


@AmitJoki Điều này cũng giống như (0).toString(không gọi hàm). Đó là thuộc tính của kiểu số.
user4642212

@AmitJoki vì nó trả lời câu hỏi 'tại sao dòng này lại hợp lệ'.
Duncan

@Duncan nhưng đây là "ký hiệu ngoặc là gì" và tôi cho rằng OP biết điều đó. Thực tế là nó hiểu là đối tượng Số lượng và sau đó nó 0sở hữu được truy cập và vì nó không tồn tại, undefinedhơn đúng như đã giải thích ở jfriend00.
Amit Joki

@AmitJoki đó là một giả định không chính xác 0[0]sẽ trả về không xác định. Có khả năng là sẽ xảy ra nhưng không nhất thiết phải như vậy
Rune FS

9

Tôi chỉ muốn lưu ý rằng cú pháp hợp lệ này không phải là duy nhất đối với Javascript. Hầu hết các ngôn ngữ sẽ có lỗi thời gian chạy hoặc lỗi kiểu, nhưng đó không phải là lỗi tương tự như lỗi cú pháp. Javascript chọn trả về không xác định trong nhiều trường hợp mà ngôn ngữ khác có thể đưa ra một ngoại lệ, bao gồm cả khi đăng ký đối tượng không có thuộc tính của tên đã cho.

Cú pháp không biết kiểu của một biểu thức (ngay cả một biểu thức đơn giản như một ký tự số), và sẽ cho phép bạn áp dụng bất kỳ toán tử nào cho bất kỳ biểu thức nào. Ví dụ: cố gắng tạo chỉ số dưới undefinedhoặc nullgây ra một TypeErrortrong Javascript. Đó không phải là lỗi cú pháp - nếu điều này không bao giờ được thực thi (nằm ở phía sai của câu lệnh if), nó sẽ không gây ra bất kỳ vấn đề nào, trong khi lỗi cú pháp theo định nghĩa luôn luôn mắc phải tại thời điểm biên dịch (eval, Function, v.v. , tất cả được tính là biên dịch).


8

Bởi vì nó là cú pháp hợp lệ, và thậm chí là mã hợp lệ để được diễn giải. Bạn có thể cố gắng truy cập bất kỳ thuộc tính nào của bất kỳ đối tượng nào (và trong trường hợp này, 0 sẽ được chuyển thành đối tượng Số) và nó sẽ cung cấp cho bạn giá trị nếu nó tồn tại, nếu không thì không xác định. Tuy nhiên, cố gắng truy cập một thuộc tính của undefined không hoạt động, vì vậy 0 [0] [0] sẽ dẫn đến lỗi thời gian chạy. Điều này vẫn sẽ được phân loại là cú pháp hợp lệ. Có một sự khác biệt về cú pháp hợp lệ và những gì sẽ không gây ra lỗi thời gian chạy / thời gian biên dịch.


3

Không chỉ cú pháp hợp lệ mà kết quả không nhất thiết phải như undefinedvậy trong hầu hết các trường hợp, nếu không phải tất cả các trường hợp lành mạnh thì sẽ như vậy. JS là một trong những ngôn ngữ hướng đối tượng thuần túy nhất. Hầu hết các ngôn ngữ gọi là OO đều hướng lớp, theo nghĩa là bạn không thể thay đổi hình thức (nó gắn với lớp) của đối tượng sau khi được tạo, chỉ có trạng thái của đối tượng. Trong JS, bạn có thể thay đổi trạng thái cũng như hình thức của đối tượng và điều này bạn làm thường xuyên hơn bạn nghĩ. Khả năng này tạo ra một số mã khá khó hiểu, nếu bạn sử dụng sai. Các chữ số là bất biến, vì vậy bạn không thể thay đổi chính đối tượng, không phải trạng thái cũng như hình thức của nó để bạn có thể làm

0[0] = 1;

là một biểu thức gán hợp lệ trả về 1 nhưng không thực sự gán bất kỳ thứ gì, Số 0là không thay đổi. Mà tự nó là hơi kỳ quặc. Bạn có thể có một biểu thức giả định hợp lệ và chính xác (có thể thực thi), không gán bất kỳ thứ gì (*). Tuy nhiên, kiểu của chữ số là một đối tượng có thể thay đổi, do đó bạn có thể thay đổi kiểu và những thay đổi sẽ chuyển xuống chuỗi nguyên mẫu.

Number[0] = 1;
//print 1 to the console
console.log(0[0]);
//will also print 1 to the console because all integers have the same type
console.log(1[0]); 

tất nhiên nó khác xa với danh mục sử dụng lành mạnh nhưng ngôn ngữ được chỉ định để cho phép điều này bởi vì trong các tình huống khác, việc mở rộng các khả năng đối tượng thực sự có ý nghĩa rất nhiều. Đó là cách các plugin jQuery kết nối với đối tượng jQuery để đưa ra một ví dụ.

(*) Nó thực sự gán giá trị 1 cho thuộc tính của một đối tượng, tuy nhiên không có cách nào bạn có thể tham chiếu đến đối tượng (xuyên tâm) đó và do đó nó sẽ được thu thập tại thẻ nexx GC


3

Trong JavaScript, mọi thứ đều là đối tượng, vì vậy khi trình thông dịch phân tích cú pháp nó, nó sẽ coi 0 như một đối tượng và cố gắng trả về 0 như một thuộc tính. Điều tương tự cũng xảy ra khi bạn cố gắng truy cập phần tử thứ 0 của true hoặc "" (chuỗi trống).

Ngay cả khi bạn đặt 0 [0] = 1, nó sẽ đặt thuộc tính và giá trị của nó trong bộ nhớ, nhưng khi bạn truy cập 0, nó được coi là một số (Đừng nhầm lẫn giữa việc coi là Đối tượng và số ở đây.)

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.