Điều gì đang xảy ra trong mã này với các đối tượng Số giữ các thuộc tính và tăng số?


246

Một tweet gần đây có chứa đoạn mã JavaScript này.

Ai đó có thể vui lòng giải thích những gì đang xảy ra trong nó từng bước?

> function dis() { return this }
undefined
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"
> five * 5
25
> five.wtf
"potato"
> five++
5
> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined
> five
6

Cụ thể, nó không rõ ràng với tôi:

  • tại sao kết quả của dis.call(5)Numbervới một loại [[PrimitiveValue]]tài sản, nhưng kết quả five++five * 5dường như chỉ là số đơn giản 525(không phải Numbers)
  • tại sao five.wtftài sản biến mất sau khi five++tăng
  • tại sao five.wtftài sản không còn có thể ổn định sau khi five++tăng, mặc dù việc five.wtf = 'potato?'gán rõ ràng đặt giá trị.

6
Haha tôi đã xem tweet đó !! Nó thật kỳ quái, tôi nghĩ bởi vì bạn nhân nó lên, nó không hoàn toàn ảnh hưởng đến đối tượng, nhưng ++dường như ảnh hưởng đến kiểu cơ bản
Callum Linington

8
Dòng nào bạn không hiểu? Prevs Posttăng? Sau khi nhân lên, nó lại là một Numberđối tượng không có wtftài sản ... Nhưng nó vẫn là một vật objectdo đó nó có thể có properties..
Rayon

25
Khi bạn gọi disvới dis.call(5), này kết thúc tốt đẹp các số nguyên thủy 5thành một đối tượng của loại Numberchứa 5, do đó đối tượng này có thể được trả lại như thisđối tượng. Việc ++chuyển đổi nó trở lại một số nguyên thủy không thể chứa các thuộc tính, do đó wtfbắt đầu không được xác định.
GSerg


5
@Rayon ... và điều đó đã thay đổi với ES6 . Vì vậy, đi về phía trước toàn bộ điều này sẽ ngừng xảy ra.
GSerg

Câu trả lời:


278

OP đây. Thật buồn cười khi thấy điều này trên Stack Overflow :)

Trước khi bước qua hành vi, điều quan trọng là phải làm rõ một số điều:

  1. Giá trị sốđối tượng Số ( a = 3vs a = new Number(3)) rất khác nhau. Một là nguyên thủy, một là một đối tượng. Bạn không thể gán thuộc tính cho nguyên thủy, nhưng bạn có thể cho các đối tượng.

  2. Sự ép buộc giữa hai người là ngầm.

    Ví dụ:

    (new Number(3) === 3)  // returns false
    (new Number(3) == 3)   // returns true, as the '==' operator coerces
    (+new Number(3) === 3) // returns true, as the '+' operator coerces
  3. Mỗi biểu thức có một giá trị trả về. Khi REPL đọc và thực thi một biểu thức, đây là những gì nó hiển thị. Các giá trị trả về thường không có nghĩa là những gì bạn nghĩ và ngụ ý những điều không đúng.

Được rồi, đi thôi.

Hình ảnh gốc của mã JavaScript

Các cam kết.

> function dis() { return this }
undefined
> five = dis.call(5)
[Number: 5]

Xác định một hàm disgọi nó với 5. Điều này sẽ thực thi hàm với 5bối cảnh ( this). Ở đây, nó bị ép buộc từ một giá trị Số thành một đối tượng Số. Điều rất quan trọng cần lưu ý là chúng tôi đã ở chế độ nghiêm ngặt, điều này sẽ không xảy ra .

> five.wtf = 'potato'
'potato'
> five.wtf
'potato'

Bây giờ chúng ta đặt thuộc tính five.wtfthành 'potato'và với năm là một đối tượng, chắc chắn nó chấp nhận Nhiệm vụ đơn giản .

> five * 5
25
> five.wtf
'potato'

Với fivetư cách là một đối tượng, tôi đảm bảo nó vẫn có thể thực hiện các phép toán số học đơn giản. Nó có thể. Do thuộc tính của nó vẫn còn dính? Đúng.

Lần lượt.

> five++
5
> five.wtf
undefined

Bây giờ chúng tôi kiểm tra five++. Thủ thuật với gia tăng postfix là toàn bộ biểu thức sẽ đánh giá theo giá trị ban đầu và sau đó tăng giá trị. Có vẻ như fivevẫn là năm, nhưng thực sự biểu thức được ước tính là năm, sau đó được đặt fivethành 6.

Không chỉ fiveđược đặt thành 6, mà nó còn bị ép lại thành giá trị Số và tất cả các thuộc tính đều bị mất. Vì nguyên thủy không thể giữ thuộc tính, five.wtfkhông xác định.

> five.wtf = 'potato?'
'potato?'
> five.wtf
undefined

Tôi một lần nữa cố gắng gán một thuộc tính wtfđể five. Giá trị trả về ngụ ý nó dính, nhưng thực tế không phải vì fivenó là giá trị Số chứ không phải đối tượng Số. Biểu thức ước tính 'potato?', nhưng khi chúng tôi kiểm tra, chúng tôi thấy nó không được chỉ định.

Sự uy tín.

> five
6

Kể từ khi tăng tiền tố, fiveđã được 6.


70
Bạn đang xem kỹ?
Waddles

3
@Nathan Long Vâng, trong Java, mà JavaScript đã mượn rất nhiều từ đầu, tất cả các nguyên thủy đều có một lớp tương đương. intInteger, ví dụ. Tôi giả sử điều này là để bạn có thể tạo một hàm doSomething(Object), và vẫn có thể cung cấp cho nó nguyên thủy. Người nguyên thủy sẽ được chuyển đổi sang các lớp tương ứng của họ trong trường hợp này. Nhưng JS không thực sự quan tâm đến các loại, vì vậy lý do có lẽ là thứ khác
Suppen

4
@Eric Điều đầu tiên ++là áp dụng ToNumber cho giá trị . So sánh một trường hợp tương tự với các chuỗi: nếu bạn có x="5", sau đó x++trả về số 5.
apsillers

2
Thực sự tất cả các phép thuật xảy ra tại dis.call(5), sự ép buộc đối tượng, tôi sẽ không bao giờ mong đợi điều đó.
mcfedr

3
@gman ngoại trừ có nhiều ảnh hưởng chi tiết hơn, bao gồm cả việc đặt tên các khối lớn của thư viện chuẩn của nó, hành vi của các loại đối tượng tiêu chuẩn và thậm chí thực tế là nó sử dụng cú pháp dấu chấm phẩy (ngay cả khi dấu chấm phẩy tùy chọn) xuất phát từ thực tế là nó được thiết kế quen thuộc với các lập trình viên Java. Vâng, nó gây nhầm lẫn cho người mới bắt đầu. Điều đó không có nghĩa là những ảnh hưởng không tồn tại.
Jules

77

Có hai cách khác nhau để biểu thị một số:

var a = 5;
var b = new Number(5);

Đầu tiên là một nguyên thủy, thứ hai một đối tượng. Đối với tất cả ý định và mục đích cả hai đều hoạt động giống nhau, ngoại trừ chúng trông khác nhau khi được in ra bàn điều khiển. Một sự khác biệt quan trọng là, với tư cách là một đối tượng, new Number(5)chấp nhận các thuộc tính mới giống như bất kỳ đồng bằng nào {}, trong khi nguyên thủy 5thì không:

a.foo = 'bar';  // doesn't stick
b.foo = 'bar';  // sticks

Về dis.call(5)phần ban đầu , vui lòng xem từ khóa "này" hoạt động như thế nào? . Chúng ta chỉ nói rằng đối số đầu tiên callđược sử dụng làm giá trị của thisvà hoạt động này buộc số đó thành Numberdạng đối tượng phức tạp hơn . * Sau đó, ++buộc nó trở lại dạng nguyên thủy, bởi vì hoạt động bổ sung +dẫn đến một nguyên thủy mới.

> five = dis.call(5)  // for all intents and purposes same as new Number(5)
Number {[[PrimitiveValue]]: 5}
> five.wtf = 'potato'
"potato"
> five.wtf
"potato"

Một Numberđối tượng chấp nhận các thuộc tính mới.

> five++

++dẫn đến một 6giá trị nguyên thủy mới ...

> five.wtf
undefined
> five.wtf = 'potato?'
"potato?"
> five.wtf
undefined

... không có và không chấp nhận các thuộc tính tùy chỉnh.

* Lưu ý rằng trong chế độ nghiêm ngặt , thisđối số sẽ được xử lý khác nhau và sẽ không được chuyển đổi thành a Number. Xem http://es5.github.io/#x10.4.3 để biết chi tiết triển khai.


2
@Pharap chắc chắn cách tốt hơn trong C / C ++, nơi bạn có thể làm #define true false. Hoặc trong Java, nơi bạn có thể xác định lại ý nghĩa của các con số. Đây là những ngôn ngữ vững chắc tốt. Trong thực tế, mọi ngôn ngữ đều có "thủ thuật" tương tự trong đó bạn nhận được một kết quả hoạt động như dự định nhưng có thể trông kỳ quặc.
VLAZ

1
@Vld Ok hãy để tôi nói lại rằng, đây là lý do tại sao tôi ghét gõ vịt.
Pharap

59

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 Numberlớp học - Ý tôi là, tại sao trên thế giới JavaScript lại có một Numberlớ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 fivetô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ỉ fivenhân lên tốt đẹp wtfvẫ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ó fivethự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 đã! fiveKhô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 * 5khô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, fivephả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 fivechính nó. fivekhô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ó fivethể 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 fivecó 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 + 16, đây là bằng chứng tôi cần để lộ rằng fivekhô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 fivecâ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. numtrong thực tế 6và tôi có thể chứng minh điều đó:

>num
6

Thời gian để xem những gì fivethực sự là:

>five
6

... Đó chính xác là những gì nó cần phải có. fivelà tốt - nhưng tôi đã tốt hơn. Nếu fivevẫn là một đối tượng có nghĩa là nó vẫn có tài sản wtfvà 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! fivebâ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 + 1giố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à fivethự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à, wtfvẫ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!


9
Quên nó đi Jake; đó là Javascript.
Seth

Tại sao chúng ta gọi các hàm xây dựng là "các lớp" trong JS?
Evolutionxbox

27
01 > function dis() { return this }
02 undefined
03 > five = dis.call(5)
04 Number {[[PrimitiveValue]]: 5}
05 > five.wtf = 'potato'
06 "potato"
07 > five.wtf
08 "potato"
09 > five * 5
10 25
11 > five.wtf
12 "potato"
13 > five++
14 5
15 > five.wtf
16 undefined
17 > five.wtf = 'potato?'
18 "potato?"
19 > five.wtf
20 undefined
21 > five
22 6

01khai báo một hàm distrả về đối tượng bối cảnh. Điều gì thisthể hiện sự thay đổi tùy thuộc vào việc bạn có sử dụng chế độ nghiêm ngặt hay không. Toàn bộ ví dụ có kết quả khác nhau nếu hàm được khai báo là:

> function dis() { "use strict"; return this }

Điều này được trình bày chi tiết trong phần 10.4.3 trong đặc tả ES5

  1. Nếu mã chức năng là mã nghiêm ngặt, hãy đặt ThisBinding thành thisArg.
  2. Khác nếu thisArg là null hoặc không xác định, đặt ThisBinding thành đối tượng toàn cục.
  3. Khác nếu Type (thisArg) không phải là Object, hãy đặt ThisBinding thành ToObject (thisArg).

02là giá trị trả về của khai báo hàm. undefinednên tự giải thích ở đây.

03biến fiveđược khởi tạo với giá trị trả về diskhi được gọi trong ngữ cảnh của giá trị nguyên thủy 5. Bởi vì diskhông ở chế độ nghiêm ngặt, dòng này giống hệt với cách gọi five = Object(5).

04Number {[[PrimitiveValue]]: 5}Giá trị trả về lẻ là biểu diễn của đối tượng bao bọc giá trị nguyên thủy5

05thuộc tính của fiveđối tượng wtfđược gán một giá trị chuỗi'potato'

06 là giá trị trả về của bài tập và nên tự giải thích.

07các fiveđối tượng wtfsở hữu đang được xem xét

08như five.wtftrước đây được đặt để 'potato'nó trở lại 'potato'đây

09các fiveđối tượng đang được nhân với giá trị nguyên thủy 5. Điều này không khác với bất kỳ đối tượng nào khác đang được nhân lên và được giải thích trong phần 11.5 của đặc tả ES5 . Đặc biệt lưu ý là cách các đối tượng được truyền tới các giá trị số, được trình bày trong một vài phần.

9,3 ToNumber :

  1. Đặt primValue là ToPrimitive (đối số đầu vào, số gợi ý).
  2. Trả về ToNumber (primValue).

9.1 ToPrimitive :

Trả về một giá trị mặc định cho Object. Giá trị mặc định của một đối tượng được lấy bằng cách gọi phương thức bên trong [[DefaultValue]] của đối tượng, chuyển qua gợi ý tùy chọn PreferredType. Hành vi của phương thức bên trong [[DefaultValue]] được xác định bởi đặc tả này cho tất cả các đối tượng ECMAScript gốc trong 8.12.8 .

8.12.8 [[DefaultValue]] :

Đặt valueOf là kết quả của việc gọi phương thức bên trong [[Get]] của đối tượng O với đối số "valueOf".

  1. Nếu IsCallable (valueOf) là đúng thì,

    1. Đặt val là kết quả của việc gọi phương thức nội bộ [[Gọi]] của valueOf, với O là giá trị này và danh sách đối số trống.
    2. Nếu val là giá trị nguyên thủy, trả về val.

Đây là tất cả cách nói vòng tròn để nói rằng valueOfhàm của đối tượng được gọi và giá trị trả về từ hàm đó được sử dụng trong phương trình. Nếu bạn thay đổi valueOfchức năng, bạn có thể thay đổi kết quả của thao tác:

> five.valueOf = function () { return 10 }
undefined
> five * 5
50

10vì hàm fives valueOfkhông thay đổi, nó trả về giá trị nguyên thủy được bao bọc 5để five * 5đánh giá 5 * 5kết quả nào trong25

11thuộc tính của fiveđối tượng wtfđược đánh giá lại mặc dù không thay đổi so với khi được gán 05.

12 'potato'

13các Postfix Tăng điều hành được kêu gọi five, mà được các giá trị số ( 5, chúng ta đã như thế nào trước đó), các cửa hàng giá trị để nó có thể được trả lại, thêm 1vào giá trị ( 6), gán giá trị five, và trả về giá trị được lưu trữ ( 5)

14 như trước đây, giá trị được trả về là giá trị trước khi nó được tăng lên

15thuộc wtftính của giá trị nguyên thủy ( 6) được lưu trữ tại biến fiveđược truy cập. Mục 15.7.5 của đặc tả ES5 định nghĩa hành vi này. Số có được các thuộc tính từ Number.prototype.

16 Number.prototypekhông có wtftài sản, nên undefinedđược trả lại

17 five.wtfđược gán một giá trị của 'potato?'. Chuyển nhượng được định nghĩa trong 11.13.1 của đặc tả ES5 . Về cơ bản giá trị được gán được trả về nhưng không được lưu trữ.

18 'potato?' đã được trả về bởi toán tử gán

19một lần nữa five, có giá trị 6được truy cập và một lần nữa Number.prototypekhông có thuộc wtftính

20 undefined Như đã giải thích ở trên

21 five được truy cập

22 6 được trả lại như được giải thích trong 13


17

Nó khá đơn giản.

function dis () { return this; }

Điều này trả về thisbối cảnh. Vì vậy, nếu bạn làm call(5)bạn chuyển số dưới dạng một đối tượng.

Các callchức năng không cung cấp luận cứ, lập luận đầu tiên bạn đưa ra là bối cảnh this. Thông thường nếu bạn muốn nó trên bối cảnh, bạn cung cấp cho nó {}như vậy dis.call({}), có nghĩa là thistrong hàm là trống this. Tuy nhiên, nếu bạn vượt qua 5có vẻ như nó sẽ được chuyển đổi thành một đối tượng. Xem .call

Vì vậy, sự trở lại là object

Khi bạn làm như vậy five * 5, JavaScript xem đối tượng fivelà kiểu nguyên thủy, do đó tương đương với 5 * 5. Thật thú vị, làm '5' * 5, nó vẫn bằng 25, vì vậy JavaScript rõ ràng đang được đúc dưới mui xe. Không có thay đổi đối với fiveloại cơ bản được thực hiện trên dòng này

Nhưng khi bạn làm điều ++đó sẽ chuyển đổi đối tượng thành kiểu nguyên thủy numberdo đó loại bỏ thuộc .wtftính. Bởi vì bạn đang ảnh hưởng đến loại cơ bản


Trả về là Số .
GSerg

++chuyển đổi và gán nó trở lại. ++bằng variable = variable + 1. Vì vậy, bạn mấtwtf
Rajesh

Các *vs ++không làm tôi bối rối cả. Có một hàm mong đợi không có đối số và trả về this, dù sao hàm đó cũng chấp nhận một đối số và trả về một cái gì đó giống như, nhưng không chính xác, đối số - điều đó không có ý nghĩa với tôi.
Nathan Long

Đã cập nhật nó @NathanLong để hiển thị những gì dis.call()không.
Callum Linington

5 ++ hoạt động giống như trong ngôn ngữ C nên không có gì đáng ngạc nhiên. thischỉ đơn giản là một con trỏ tới một đối tượng để các kiểu nguyên thủy được chuyển đổi hoàn toàn. Tại sao một hàm không có đối số không thể có ít nhất một bối cảnh? Đối số 1 của callhoặc bindđược sử dụng để đặt bối cảnh. Ngoài ra, các chức năng là đóng cửa có nghĩa là chúng có quyền truy cập nhiều hơn chỉarguments
Rivenfall

10

Các giá trị nguyên thủy không thể có tài sản. Nhưng khi bạn cố gắng truy cập vào một thuộc tính trên giá trị nguyên thủy, nó sẽ chuyển sang dạng đối tượng Số tạm thời.

Vì thế:

> function dis() { return this }
undefined
// Like five.dis(), so dis return the temporaty Number object and 
// reference it in five
> five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

// Write the wtf attribut on the Number object referenced by five
> five.wtf = 'potato'
"potato"
// Read the wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Return 5*5 but dont change the reference of five
> five * 5
25
// Read the same wtf attribut on the Number object referenced by five
> five.wtf
"potato"

// Change the five reference to a new primitive value (5+1). Five
// reference a primitive now.
> five++
5

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined

// Write the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. But this object not referenced by
// five. It will be lost.
> five.wtf = 'potato?'
"potato?"

// Read the wtf attribut on a new temporary Number object construct from
// the primitive referenced by five. So wtf does not exist.
> five.wtf
undefined
> five
6

8

Khai báo hàm dis. Hàm trả về bối cảnh của nó

function dis() { return this }
undefined

Gọi disvới ngữ cảnh 5. Các giá trị nguyên thủy được đóng hộp khi được chuyển dưới dạng bối cảnh ở chế độ nghiêm ngặt ( MDN ). Vì vậy, fivebây giờ là đối tượng (số hộp).

five = dis.call(5)
Number {[[PrimitiveValue]]: 5}

Khai báo wtftài sản trên fivebiến

five.wtf = 'potato'
"potato"

Giá trị của five.wtf

five.wtf
"potato"

fiveđược đóng hộp 5, do đó, số và đối tượng cùng một lúc (5 * 5 = 25). Nó không thay đổi five.

five * 5
25

Giá trị của five.wtf

five.wtf
"potato"

Bỏ hộp fiveở đây. fivebây giờ chỉ là nguyên thủy number. Nó in 5, và sau đó thêm 1vào five.

five++
5

fivelà số nguyên thủy 6bây giờ, không có thuộc tính trong đó.

five.wtf
undefined

nguyên thủy không thể có thuộc tính, bạn không thể thiết lập điều này

five.wtf = 'potato?'
"potato?"

bạn không thể đọc nó, bởi vì nó không được đặt

five.wtf
undefined

five6vì bài tăng dần ở trên

five
6

7

Trước hết, có vẻ như điều này đang được chạy qua bảng điều khiển nodejs.

1.

    function dis() { return this }

tạo hàm dis (), nhưng vì nó không được đặt varlà không có giá trị trả về nên undefinedđầu ra, mặc dù dis()đã được xác định. Trên một sidenote, thisđã không được trả về vì chức năng đã không được thực thi.

2.

    five = dis.call(5)

Lợi nhuận này javascript của Numberđối tượng vì bạn chỉ cần thiết lập các chức năng dis()của thisgiá trị nguyên thủy năm.

3.

   five.wtf = 'potato'

Trở về đầu tiên "potato"bởi vì bạn chỉ cần thiết lập thuộc tính wtfcủa fiveđể 'potato'. Javascript trả về giá trị của một biến bạn đặt, giúp dễ dàng xâu chuỗi nhiều biến và đặt chúng thành cùng một giá trị như thế này : a = b = c = 2.

4.

    five * 5

Điều này trả về 25vì bạn chỉ cần nhân số nguyên thủy 5với five. Giá trị của fiveđược xác định bởi giá trị của Numberđối tượng.

5.

    five.wtf

Tôi đã bỏ qua dòng này trước đây vì tôi sẽ lặp lại nó ở đây. Nó chỉ trả về giá trị của tài sản wtfmà bạn đặt ở trên.

6.

    five++

Như @Callum đã nói, ++sẽ chuyển đổi loại thành numbertừ cùng một giá trị từ đối tượng Number {[[PrimitiveValue]]: 5}}.

Bây giờ vì fivelà một number, bạn không thể đặt thuộc tính cho nó nữa cho đến khi bạn làm một cái gì đó như thế này:

    five = dis.call(five)
    five.wtf = "potato?"

hoặc là

    five = { value: 6, wtf: "potato?" }

Cũng lưu ý rằng cách thứ hai sẽ có hành vi khác với sử dụng phương thức thứ nhất vì nó đang xác định một đối tượng chung thay vì Numberđối tượng đã được tạo trước đó.

Tôi hy vọng điều này có ích, javascript thích giả định mọi thứ, vì vậy nó có thể gây nhầm lẫn khi thay đổi từ Numberđối tượng sang nguyên thủy number. Bạn có thể kiểm tra loại gì đó bằng cách sử dụng typeoftừ khóa, viết loại năm sau khi bạn khởi tạo nó trở lại 'object'và sau khi bạn thực hiện five++nó trở lại 'number'.

@deceze mô tả sự khác biệt giữa đối tượng Số và số nguyên thủy cực kỳ tốt.


6

Phạm vi JavaScript được tạo từ các bối cảnh thực thi. Mỗi Bối cảnh thực thi có Môi trường từ điển (giá trị phạm vi bên ngoài / toàn cầu), Môi trường biến (giá trị phạm vi cục bộ) và ràng buộc này .

Các này ràng buộc là một phần rất quan trọng của bối cảnh Thực hiện. Sử dụng calllà một cách để thay đổi liên kết này và làm như vậy sẽ tự động tạo một đối tượng để điền vào liên kết.

Hàm.prototype.call () (từ MDN)

Cú pháp
fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg
Giá trị này được cung cấp cho cuộc gọi để vui vẻ. Lưu ý rằng đây có thể không phải là giá trị thực mà phương thức nhìn thấy: nếu phương thức là một hàm trong mã chế độ không nghiêm ngặt, null và không xác định sẽ được thay thế bằng đối tượng toàn cục và các giá trị nguyên thủy sẽ được chuyển đổi thành các đối tượng . (nhấn mạnh của tôi)

Một khi rõ ràng là 5 đang được chuyển đổi thành new Number(5), phần còn lại sẽ khá rõ ràng. Lưu ý rằng các ví dụ khác cũng sẽ hoạt động miễn là chúng là các giá trị nguyên thủy.

function primitiveToObject(prim){
  return dis.call(prim);
}
function dis(){ return this; }

//existing example
console.log(primitiveToObject(5));

//Infinity
console.log(primitiveToObject(1/0));

//bool
console.log(primitiveToObject(1>0));

//string
console.log(primitiveToObject("hello world"));
<img src="http://i.stack.imgur.com/MUyRV.png" />

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


4

Một vài khái niệm giải thích những gì xảy ra

5 là một con số, một giá trị nguyên thủy

Number {[[PrimitiveValue]]: 5} là một thể hiện của Number (hãy gọi nó là trình bao bọc đối tượng)

Bất cứ khi nào bạn truy cập một thuộc tính / phương thức trên một giá trị nguyên thủy, công cụ JS sẽ tạo một trình bao bọc đối tượng thuộc loại thích hợp ( Numbercho 5, Stringcho 'str'Booleancho true) và giải quyết lệnh gọi phương thức / truy cập thuộc tính trên trình bao bọc đối tượng đó. Đây là những gì xảy ra khi bạn làm true.toString()ví dụ.

Khi thực hiện các thao tác trên các đối tượng, chúng được chuyển đổi thành các giá trị nguyên thủy (bằng cách sử dụng toStringhoặc valueOf) để giải quyết các hoạt động đó - ví dụ như khi thực hiện

var obj = { a : 1 };
var string = 'mystr' + obj;
var number = 3 + obj;

stringsẽ giữ chuỗi nối mystrobj.toString()numbersẽ giữ thêm 3obj.valueOf().

Bây giờ để đặt tất cả lại với nhau

five = dis.call(5)

dis.call(5)cư xử giống như (5).dis()nếu 5thực sự có phương pháp dis. Để giải quyết cuộc gọi phương thức, trình bao bọc đối tượng được tạo và cuộc gọi phương thức được giải quyết trên nó. Tại thời điểm này, năm điểm đến một trình bao bọc đối tượng xung quanh giá trị nguyên thủy 5.

five.wtf = 'potato'

Đặt thuộc tính trên một đối tượng, không có gì lạ mắt ở đây.

five * 5

Điều này thực sự five.valueOf() * 5có được giá trị nguyên thủy từ trình bao bọc đối tượng. fivevẫn chỉ vào đối tượng ban đầu.

five++

Đây là thực sự five = five.valueOf() + 1. Trước dòng này fivegiữ trình bao bọc đối tượng xung quanh giá trị 5, trong khi sau điểm này fivegiữ giá trị nguyên thủy6 .

five.wtf
five.wtf = 'potato?'
five.wtf

fivekhông còn là một đối tượng. Mỗi dòng đó tạo ra một thể hiện mới của Số để giải quyết .wtfquyền truy cập thuộc tính. Các thể hiện là độc lập, do đó, đặt thuộc tính trên một thuộc tính sẽ không hiển thị trên một thuộc tính khác. Mã này hoàn toàn tương đương với mã này:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
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.