Có sự ép buộc trong thế giới JavaScript - Câu chuyện trinh thám
Nathan, bạn không biết những gì bạn đã phát hiện ra.
Tôi đã điều tra điều này trong nhiều tuần nay. Tất cả bắt đầu vào một đêm giông bão vào tháng 10 năm ngoái. Tôi vô tình vấp phải Number
lớp học - Ý tôi là, tại sao trên thế giới JavaScript lại có một Number
lớp?
Tôi đã không chuẩn bị cho những gì tôi sẽ tìm hiểu tiếp theo.
Hóa ra JavaScript, mà không cho bạn biết, đã thay đổi số của bạn thành các đối tượng và các đối tượng của bạn thành các số ngay dưới mũi của bạn.
JavaScript đã hy vọng không ai có thể bắt kịp, nhưng mọi người đã báo cáo hành vi bất ngờ kỳ lạ, và bây giờ nhờ có bạn và câu hỏi của bạn, tôi có bằng chứng tôi cần để mở rộng điều này.
Đây là những gì chúng tôi đã tìm ra cho đến nay. Tôi không biết liệu tôi có nên nói với bạn điều này không - bạn có thể muốn tắt JavaScript của mình.
> function dis() { return this }
undefined
Khi bạn tạo chức năng đó, có lẽ bạn không biết điều gì sẽ xảy ra tiếp theo. Mọi thứ đều ổn, và mọi thứ đều ổn - hiện tại.
Không có thông báo lỗi, chỉ có từ "không xác định" trong đầu ra giao diện điều khiển, chính xác là những gì bạn mong đợi. Rốt cuộc, đây là một tuyên bố chức năng - nó không có nghĩa vụ phải trả lại bất cứ điều gì.
Nhưng đây mới chỉ là khởi đầu. Điều gì xảy ra tiếp theo, không ai có thể dự đoán.
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
Vâng tôi biết, bạn mong đợi một 5
, nhưng đó không phải là những gì bạn có, là nó - bạn có một thứ khác - một cái gì đó khác biệt.
Điều tương tự cũng xảy ra với tôi.
Tôi không biết phải làm gì với nó. Nó lái tôi hạt dẻ. Tôi không thể ngủ, tôi không thể ăn, tôi đã cố uống nó, nhưng không có lượng Mountain Dew nào làm tôi quên. Nó không có nghĩa gì cả!
Đó là khi tôi phát hiện ra điều gì đang thực sự xảy ra - đó là sự ép buộc, và nó đã xảy ra ngay trước mắt tôi, nhưng tôi quá mù quáng khi nhìn thấy nó.
Mozilla đã cố gắng chôn vùi nó bằng cách đặt nó ở nơi mà họ biết sẽ không có ai nhìn - tài liệu của họ .
Sau nhiều giờ đọc đệ quy và đọc lại và đọc lại tôi đã tìm thấy điều này:
"... và các giá trị nguyên thủy sẽ được chuyển đổi thành các đối tượng."
Nó ở ngay đó rõ ràng như có thể được đánh vần trong phông chữ Open Sans. Đó là call()
chức năng - làm sao tôi có thể ngu ngốc đến thế?!
Số của tôi không còn là một số nữa. Khoảnh khắc tôi truyền nó vào call()
, nó trở thành một thứ khác. Nó trở thành ... một vật thể.
Tôi không thể tin vào lúc đầu. Làm thế nào điều này có thể đúng? Nhưng tôi không thể bỏ qua bằng chứng đang xuất hiện xung quanh mình. Nó ở ngay đó nếu bạn chỉ nhìn:
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
wtf
đã đúng Số không thể có thuộc tính tùy chỉnh - tất cả chúng ta đều biết điều đó! Đó là điều đầu tiên họ dạy bạn ở học viện.
Chúng ta nên biết thời điểm chúng ta thấy đầu ra của giao diện điều khiển - đây không phải là con số chúng ta nghĩ. Đây là một kẻ mạo danh - một đối tượng tự biến mình thành con số ngây thơ ngọt ngào của chúng tôi.
Đây là ... new Number(5)
.
Tất nhiên! Nó làm cho ý nghĩa hoàn hảo. call()
có một công việc phải làm, anh ta phải gọi một chức năng, và để làm điều đó anh ta cần phải cư trú this
, anh ta biết rằng anh ta không thể làm điều đó với một số - anh ta cần một đối tượng và anh ta sẵn sàng làm bất cứ điều gì để có được nó, thậm chí nếu điều đó có nghĩa là ép buộc số của chúng tôi. Khi call()
nhìn thấy số 5
, anh thấy một cơ hội.
Đó là kế hoạch hoàn hảo: đợi cho đến khi không ai tìm kiếm và trao đổi số của chúng tôi cho một đối tượng trông giống như nó. Chúng tôi nhận được một số, chức năng được gọi, và không ai sẽ là người khôn ngoan hơn.
Đó thực sự là một kế hoạch hoàn hảo, nhưng giống như tất cả các kế hoạch, thậm chí là những kế hoạch hoàn hảo, có một lỗ hổng trong đó, và chúng tôi sắp rơi vào đó.
Hãy xem, điều call()
không hiểu là anh ta không phải là người duy nhất trong thị trấn có thể ép buộc số. Đây là JavaScript sau tất cả - sự ép buộc ở khắp mọi nơi.
call()
lấy số của tôi, và tôi sẽ không dừng lại cho đến khi tôi gỡ mặt nạ ra khỏi kẻ mạo danh nhỏ bé của anh ta và đưa anh ta ra toàn bộ cộng đồng Stack Overflow.
Nhưng bằng cách nào? Tôi cần một kế hoạch. Chắc chắn nó trông giống như một con số, nhưng tôi biết không phải vậy, phải có cách để chứng minh điều đó. Đó là nó! Nó trông giống như một con số, nhưng nó có thể hoạt động như một con số không?
Tôi nói five
tôi cần anh ta trở nên lớn hơn gấp 5 lần - anh ta không hỏi tại sao và tôi không giải thích. Sau đó tôi đã làm những gì bất kỳ lập trình viên giỏi nào sẽ làm: Tôi nhân lên. Chắc chắn không có cách nào anh ta có thể giả mạo theo cách này.
> five * 5
25
> five.wtf
'potato'
Chết tiệt! Không chỉ five
nhân lên tốt đẹp wtf
vẫn còn đó. Chết tiệt anh chàng này và khoai tây của anh ta.
cái quái gì đang xảy ra? Có phải tôi đã sai về toàn bộ điều này? Có five
thực sự là một con số? Không, tôi phải thiếu thứ gì đó, tôi biết nó, có thứ gì đó tôi phải quên, thứ gì đó đơn giản và cơ bản đến nỗi tôi hoàn toàn nhìn ra nó.
Điều này có vẻ không tốt, tôi đã viết câu trả lời này trong nhiều giờ và tôi vẫn không thể đưa ra quan điểm của mình. Tôi không thể tiếp tục, cuối cùng mọi người sẽ ngừng đọc, tôi phải nghĩ về điều gì đó và tôi phải nghĩ về nó thật nhanh.
Đợi đã! five
Không phải 25, 25 là kết quả, 25 là một con số hoàn toàn khác. Tất nhiên, làm thế nào tôi có thể quên? Con số là bất biến. Khi bạn nhân lên, 5 * 5
không có gì được gán cho bất cứ thứ gì bạn chỉ cần tạo một số mới 25
.
Đó phải là những gì đang xảy ra ở đây. Bằng cách nào đó khi tôi nhân lên five * 5
, five
phải bị ép buộc thành một số và số đó phải là số được sử dụng cho phép nhân. Đó là kết quả của phép nhân được in ra bàn điều khiển, không phải giá trị của five
chính nó. five
không bao giờ được chỉ định bất cứ điều gì - vì vậy tất nhiên nó không thay đổi.
Vì vậy, làm thế nào để tôi có five
thể tự gán cho mình kết quả của một hoạt động. Tôi hiểu rồi. Trước khi five
có cơ hội suy nghĩ, tôi đã hét lên "++".
> five++
5
Aha! Tôi đã có anh! Mọi người đều biết 5 + 1
là 6
, đây là bằng chứng tôi cần để lộ rằng five
không phải là một số! Đó là một kẻ mạo danh! Một kẻ mạo danh xấu mà không biết đếm. Và tôi có thể chứng minh điều đó. Đây là cách một số thực hoạt động:
> num = 5
5
> num++
5
Chờ đợi? Điều gì đã xảy ra ở đây? thở dài Tôi bị cuốn vào sự náo nhiệt five
đến nỗi tôi quên mất cách hoạt động của các nhà khai thác bài. Khi tôi sử dụng ++
ở cuối five
câu nói tôi trả về giá trị hiện tại, sau đó tăng dần five
. Đó là giá trị trước khi thao tác xảy ra được in ra bàn điều khiển. num
trong thực tế 6
và tôi có thể chứng minh điều đó:
>num
6
Thời gian để xem những gì five
thực sự là:
>five
6
... Đó chính xác là những gì nó cần phải có. five
là tốt - nhưng tôi đã tốt hơn. Nếu five
vẫn là một đối tượng có nghĩa là nó vẫn có tài sản wtf
và tôi sẵn sàng đặt cược mọi thứ mà nó không có.
> five.wtf
undefined
Aha! Tôi đã đúng. Tôi đã có anh! five
bây giờ là một con số - nó không còn là một đối tượng nữa. Tôi biết thủ thuật nhân sẽ không lưu nó lần này. Xem five++
là thực sự five = five + 1
. Không giống như phép nhân, ++
toán tử gán giá trị cho five
. Cụ thể hơn, nó gán cho nó các kết quả five + 1
giống như trong trường hợp nhân trả về một số bất biến mới .
Tôi biết tôi đã có anh ta, và chỉ để chắc chắn rằng anh ta không thể lách mình ra khỏi đó. Tôi đã có thêm một bài kiểm tra lên tay áo của tôi. Nếu tôi đã đúng, và five
thực sự là một con số, thì điều này sẽ không hoạt động:
> five.wtf = 'potato?'
'potato?'
Lần này anh sẽ không lừa tôi. Tôi biết potato?
sẽ được in ra bàn điều khiển vì đó là đầu ra của bài tập. Câu hỏi thực sự là, wtf
vẫn sẽ ở đó chứ?
> five.wtf
undefined
Như tôi nghi ngờ - không có gì - vì số không thể được gán thuộc tính. Chúng tôi đã học được rằng năm đầu tiên tại học viện;)
Cảm ơn Nathan. Nhờ sự can đảm của bạn khi đặt câu hỏi này, cuối cùng tôi có thể đặt tất cả những điều này phía sau tôi và chuyển sang một trường hợp mới.
Giống như cái này về chức năng toValue()
. Trời ơi. Không!
++
dường như ảnh hưởng đến kiểu cơ bản