เกิดอะไรขึ้นในรหัสนี้กับวัตถุหมายเลขถือคุณสมบัติและการเพิ่มจำนวน?


246

ทวีตล่าสุดมีตัวอย่างของ JavaScript นี้

ใครช่วยกรุณาอธิบายสิ่งที่เกิดขึ้นในแต่ละขั้นตอน

> 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

โดยเฉพาะอย่างยิ่งมันไม่ชัดเจนสำหรับฉัน:

  • ทำไมผลลัพธ์ของdis.call(5)คือ a Numberมีคุณสมบัติบางอย่าง[[PrimitiveValue]]แต่ผลลัพธ์ของfive++และfive * 5ดูเหมือนจะเป็นตัวเลขธรรมดา5และ25(ไม่ใช่Numbers)
  • เหตุใดfive.wtfทรัพย์สินจึงหายไปหลังจากการfive++เพิ่มขึ้น
  • เหตุใดfive.wtfคุณสมบัติจึงไม่สามารถตั้งค่าได้อีกหลังจากการfive++เพิ่มขึ้นแม้จะมีการfive.wtf = 'potato?'กำหนดค่าให้ชัดเจน

6
ฮ่าฮ่าฉันเห็นทวีตนั้นแล้ว !! มันแปลกประหลาดมากฉันคิดว่าเพราะคุณคูณมันไม่ส่งผลกระทบต่อวัตถุ แต่++ดูเหมือนว่าจะส่งผลกระทบต่อชนิดพื้นฐาน
Callum Linington

8
สายไหนที่คุณไม่เข้าใจ? PreเทียบกับการPostเพิ่มขึ้น? หลังจากการคูณมันเป็นNumberวัตถุที่ไม่มีwtfสมบัติ ... อีกครั้งแต่ก็ยังเป็นobjectเพราะมันสามารถproperties..
Rayon

25
เมื่อคุณเรียกdisใช้dis.call(5)ฟังก์ชันนี้จะตัดตัวเลขดั้งเดิม5เป็นวัตถุชนิดNumberที่มีเครื่องหมาย5เพื่อให้วัตถุนี้สามารถส่งคืนเป็นthisวัตถุได้ การ++แปลงกลับเป็นหมายเลขดั้งเดิมที่ไม่สามารถมีคุณสมบัติได้ดังนั้นจึงwtfเริ่มไม่ได้กำหนด
GSerg


5
@Rayon ... และที่มีการเปลี่ยนแปลงกับ ES6 ดังนั้นการก้าวไปข้างหน้าสิ่งทั้งหมดนี้จะไม่เกิดขึ้น
GSerg

คำตอบ:


278

OP ที่นี่ ตลกที่เห็นสิ่งนี้ใน Stack Overflow :)

ก่อนที่จะก้าวผ่านพฤติกรรมมันเป็นสิ่งสำคัญที่จะชี้แจงบางสิ่ง:

  1. ค่าตัวเลขและวัตถุหมายเลข ( a = 3vs a = new Number(3)) แตกต่างกันมาก หนึ่งคือดั้งเดิมดั้งเดิมอื่น ๆ เป็นวัตถุ คุณไม่สามารถกำหนดแอตทริบิวต์ให้กับวัตถุพื้นฐานได้ แต่คุณสามารถทำได้กับวัตถุ

  2. การบีบบังคับระหว่างคนทั้งสองนั้นเป็นการบอกเป็นนัย

    ตัวอย่างเช่น:

    (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. ทุกนิพจน์มีค่าส่งคืน เมื่อREPLอ่านและดำเนินการนิพจน์นี่คือสิ่งที่มันจะแสดง ค่าที่ส่งคืนมักไม่ได้หมายความว่าคุณคิดและบอกถึงสิ่งที่ไม่เป็นความจริง

ตกลงไปเลย

ภาพต้นฉบับของรหัส JavaScript

จำนำ.

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

กำหนดฟังก์ชั่นdisและเรียก5มันด้วย สิ่งนี้จะดำเนินการฟังก์ชันด้วย5ตามบริบท ( this) ที่นี่มันถูกข่มขู่จากค่า Number ให้กับวัตถุ Number มันเป็นสิ่งสำคัญมากที่จะทราบว่ามีเราอยู่ในโหมดที่เข้มงวด นี้จะไม่เกิดขึ้น

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

ตอนนี้เราตั้งค่าแอตทริบิวต์five.wtfการ'potato'และมีห้าเป็นวัตถุนั่นเองค่ะยอมรับการมอบหมายงานที่เรียบง่าย

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

ด้วยfiveวัตถุฉันมั่นใจได้ว่ามันยังสามารถทำการคำนวณทางคณิตศาสตร์ได้ง่าย มันสามารถ คุณลักษณะของมันยังคงติดอยู่ไหม? ใช่.

การเลี้ยว

> five++
5
> five.wtf
undefined

five++ตอนนี้เราตรวจสอบ เคล็ดลับที่มีการเพิ่ม postfixคือการแสดงออกทั้งหมดจะประเมินเทียบกับค่าเดิมแล้วเพิ่มค่า ดูเหมือนว่าfiveยังคงเป็นห้า แต่จริงๆแสดงออกประเมินถึงห้าแล้วตั้งไปfive6

ไม่เพียง แต่fiveตั้งค่าเป็น6แต่มันถูกรวมกลับเป็นค่าตัวเลขและคุณลักษณะทั้งหมดจะสูญหายไป ตั้งแต่ดั้งเดิมไม่สามารถเก็บคุณลักษณะfive.wtfจึงไม่ได้กำหนด

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

ฉันพยายามกำหนดแอตทริบิวต์wtfให้fiveอีกครั้ง ค่าส่งคืนหมายถึงมันเกาะติด แต่ในความเป็นจริงมันไม่ได้เพราะfiveเป็นค่าจำนวนไม่ใช่วัตถุจำนวน นิพจน์ประเมินเป็น'potato?'แต่เมื่อเราตรวจสอบว่าเราเห็นว่าไม่ได้รับมอบหมาย

ศักดิ์ศรี.

> five
6

นับตั้งแต่การเพิ่ม postfix ที่ได้รับfive6


70
คุณกำลังดูอย่างใกล้ชิด?
Waddles

3
@Nathan Long Well ใน Java ซึ่ง JavaScript ยืมมาอย่างมากตั้งแต่ต้นดั้งเดิมทั้งหมดมีระดับเทียบเท่ากัน intและIntegerตัวอย่างเช่น ฉันถือว่านี่คือเพื่อให้คุณสามารถสร้างฟังก์ชั่นdoSomething(Object)และยังสามารถที่จะให้มันดั้งเดิม Primitives จะถูกแปลงเป็นคลาสที่เกี่ยวข้องในกรณีนี้ แต่ JS ไม่ได้สนใจเรื่องประเภทดังนั้นเหตุผลอาจเป็นอย่างอื่น
เสริมสุข

4
@ Eric สิ่งแรกที่++ทำคือใช้ ToNumber ค่า เปรียบเทียบกรณีที่คล้ายกันกับสตริง: ถ้าคุณมีx="5"แล้วส่งกลับจำนวนx++ 5
apsillers

2
ความมหัศจรรย์ทั้งหมดเกิดขึ้นที่dis.call(5)การบีบบังคับต่อวัตถุฉันไม่เคยคาดคิดมาก่อน
mcfedr

3
@gman ยกเว้นว่ามีรายละเอียดมากมายที่มีอิทธิพลมากมายรวมถึงการตั้งชื่อกลุ่มขนาดใหญ่ของไลบรารีมาตรฐานพฤติกรรมของประเภทวัตถุมาตรฐานและความจริงที่ว่ามันใช้ไวยากรณ์ของเครื่องหมายวงเล็บและเซมิโคลอน (แม้ว่าอัฒภาคจะเป็น ทางเลือก) มาจากความจริงที่ว่ามันถูกออกแบบมาให้คุ้นเคยกับโปรแกรมเมอร์ Java ใช่มันสับสนสำหรับผู้เริ่มต้น นั่นไม่ได้หมายความว่าไม่มีอิทธิพล
จูลส์

77

มีสองวิธีในการแทนตัวเลข:

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

แรกคือดั้งเดิมวัตถุที่สอง สำหรับความตั้งใจและวัตถุประสงค์ทั้งหมดต่างก็มีพฤติกรรมเหมือนกันยกเว้นพวกมันจะดูแตกต่างกันเมื่อพิมพ์ไปที่คอนโซล ความแตกต่างที่สำคัญอย่างหนึ่งคือในฐานะที่เป็นวัตถุnew Number(5)ยอมรับคุณสมบัติใหม่เช่นเดียวกับวัตถุธรรมดา{}ในขณะที่ดั้งเดิม5ไม่ได้:

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

สำหรับdis.call(5)ส่วนเริ่มต้นโปรดดูคำหลัก "นี้" ทำงานอย่างไร . สมมติว่าอาร์กิวเมนต์แรกที่callใช้เป็นค่าของthisและการดำเนินการนี้บังคับให้ตัวเลขในNumberรูปแบบวัตถุที่ซับซ้อนยิ่งขึ้น* หลังจากนั้น++บังคับให้มันกลับสู่รูปแบบดั้งเดิมเนื่องจากการดำเนินการเพิ่มเติมให้+ผลลัพธ์แบบดั้งเดิมใหม่

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

Numberวัตถุยอมรับคุณสมบัติใหม่

> five++

++ผลลัพธ์ใน6ค่าดั้งเดิมใหม่...

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

... ซึ่งไม่มีและไม่ยอมรับแอตทริบิวต์ที่กำหนดเอง

* หมายเหตุว่าในโหมดเข้มงวดthisอาร์กิวเมนต์จะได้รับการปฏิบัติที่แตกต่างกันและจะไม่Numberถูกแปลงเป็น ดูhttp://es5.github.io/#x10.4.3สำหรับรายละเอียดการใช้งาน


2
@Pharap วิธีการตรวจสอบที่ดีขึ้นใน C / C ++ #define true falseที่คุณสามารถทำได้ หรือในชวาที่คุณสามารถกำหนดความหมายของตัวเลขใหม่ได้ ภาษาเหล่านี้เป็นภาษาที่ดี ในความเป็นจริงทุกภาษามี "ลูกเล่น" ที่คล้ายกันซึ่งคุณจะได้รับผลลัพธ์ที่ได้ตามที่ตั้งใจ แต่อาจดูแปลก ๆ
VLAZ

1
@Vld Ok ให้ฉันใช้ถ้อยคำใหม่นั่นคือเหตุผลที่ฉันเกลียดการพิมพ์เป็ด
Pharap

59

มีการข่มขู่ในโลก JavaScript - เรื่องราวนักสืบ

นาธานคุณไม่รู้หรอกว่าคุณค้นพบอะไร

ฉันได้ตรวจสอบเรื่องนี้มาหลายสัปดาห์แล้ว ทุกอย่างเริ่มต้นในคืนที่มีพายุเมื่อเดือนตุลาคมที่ผ่านมา ฉันบังเอิญเข้าNumberชั้นเรียน - ฉันหมายความว่าทำไม JavaScript ถึงมีNumberระดับโลก?

ฉันไม่ได้เตรียมตัวสำหรับสิ่งที่ฉันจะค้นหาต่อไป

ปรากฎว่า JavaScript โดยไม่บอกคุณได้เปลี่ยนหมายเลขของคุณเป็นวัตถุและวัตถุของคุณเป็นตัวเลขภายใต้จมูกของคุณ

จาวาสคริปต์กำลังหวังว่าจะไม่มีใครจับ แต่คนได้รับการรายงานพฤติกรรมที่ไม่คาดคิดที่แปลกและตอนนี้ขอบคุณคุณและคำถามของคุณฉันมีหลักฐานที่ฉันต้องระเบิดสิ่งนี้เปิดกว้าง

นี่คือสิ่งที่เราค้นพบ ฉันไม่รู้ว่าฉันควรจะบอกเรื่องนี้กับคุณหรือไม่ - คุณอาจต้องการปิดจาวาสคริปต์ของคุณ

> function dis() { return this }
undefined

เมื่อคุณสร้างฟังก์ชั่นนั้นคุณอาจไม่รู้เลยว่าจะเกิดอะไรขึ้นต่อไป ทุกอย่างดูดีและทุกอย่างเรียบร้อยดี - ตอนนี้

ไม่มีข้อความแสดงข้อผิดพลาดเพียงแค่คำว่า "undefined" ในเอาต์พุตคอนโซลสิ่งที่คุณคาดหวัง ท้ายที่สุดนี่เป็นการประกาศฟังก์ชั่น - มันไม่ควรจะคืนอะไรเลย

แต่นี่เป็นเพียงจุดเริ่มต้น เกิดอะไรขึ้นต่อไปไม่มีใครสามารถทำนายได้

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

ใช่ฉันรู้คุณคาดหวัง5แต่นั่นไม่ใช่สิ่งที่คุณได้รับมัน - คุณมีอย่างอื่น - สิ่งที่แตกต่าง

สิ่งเดียวกันเกิดขึ้นกับฉัน

ฉันไม่รู้จะทำยังไง มันทำให้ฉันถั่ว ฉันนอนไม่หลับฉันกินไม่ได้ฉันพยายามที่จะดื่ม แต่ไม่มี Mountain Dew ที่จะทำให้ฉันลืม มันไม่สมเหตุสมผลเลย!

นั่นคือตอนที่ฉันรู้ว่าเกิดอะไรขึ้นจริง ๆ - มันเป็นการบีบบังคับและมันเกิดขึ้นตรงนั้นต่อหน้าต่อตาของฉัน แต่ฉันก็ตาบอดเกินกว่าจะมองเห็นได้

Mozilla พยายามที่จะฝังมันโดยการวางไว้ในที่ที่พวกเขารู้ว่าไม่มีใครจะมอง - พวกเขาเอกสาร

หลังจากชั่วโมงของการอ่านซ้ำและการอ่านซ้ำและการอ่านซ้ำฉันพบสิ่งนี้:

"... และค่าดั้งเดิมจะถูกแปลงเป็นวัตถุ"

มันก็ธรรมดาตรงที่สามารถสะกดได้ด้วยฟอนต์ Open Sans มันเป็นcall()ฟังก์ชั่น - ฉันจะโง่ได้ยังไง!

หมายเลขของฉันไม่ใช่หมายเลขอีกต่อไป เมื่อฉันผ่านมันไปcall()มันกลายเป็นอย่างอื่น มันกลายเป็น ... วัตถุ

ตอนแรกฉันไม่อยากจะเชื่อเลย สิ่งนี้จะเป็นจริงได้อย่างไร? แต่ฉันไม่สามารถเพิกเฉยต่อหลักฐานที่มีอยู่รอบตัวฉัน มันอยู่ที่นั่นถ้าคุณเพิ่งดู:

> five.wtf = 'potato'
"potato"

> five.wtf
"potato"

wtfถูกต้อง ตัวเลขไม่สามารถมีคุณสมบัติที่กำหนดเองได้ - เราทุกคนรู้ว่า! มันเป็นสิ่งแรกที่พวกเขาสอนคุณที่สถานศึกษา

เราควรรู้ทันทีที่เห็นคอนโซลเอาท์พุท - นี่ไม่ใช่ตัวเลขที่เราคิดว่ามันเป็น นี่คือนักต้มตุ๋น - วัตถุผ่านตัวมันเองไปตามหมายเลขบริสุทธิ์ที่ไร้เดียงสาของเรา

นี่คือ new Number(5)...

แน่นอน! มันทำให้รู้สึกที่สมบูรณ์แบบ call()มีงานต้องทำเขาต้องเรียกใช้ฟังก์ชั่นและเพื่อทำสิ่งที่เขาต้องการในการเติมthisเขารู้ว่าเขาไม่สามารถทำได้ด้วยตัวเลข - เขาต้องการวัตถุและเขาก็เต็มใจทำทุกอย่างเพื่อให้ได้มาด้วยซ้ำ ถ้านั่นหมายถึงการบังคับหมายเลขของเรา เมื่อcall()เห็นตัวเลข5เขาเห็นโอกาส

มันเป็นแผนการที่สมบูรณ์แบบ: รอจนกว่าจะไม่มีใครดูและสลับหมายเลขของเราเพื่อหาวัตถุที่มีลักษณะเหมือนมัน เราได้จำนวนฟังก์ชั่นจะถูกเรียกใช้และไม่มีใครฉลาดกว่า

มันเป็นแผนที่สมบูรณ์แบบจริงๆ แต่ก็เหมือนกับทุกแผนแม้แต่แผนการที่สมบูรณ์แบบมีช่องโหว่อยู่ในนั้นและเรากำลังจะตกลงไป

ดูสิสิ่งที่call()ไม่เข้าใจคือเขาไม่ใช่คนเดียวในเมืองที่สามารถบีบบังคับตัวเลขได้ นี่คือ JavaScript หลังจากทั้งหมด - การบีบบังคับอยู่ทุกที่

call() รับหมายเลขของฉันและฉันจะไม่หยุดจนกว่าฉันจะดึงหน้ากากออกมาจากนักต้มตุ๋นตัวน้อยของเขาและเปิดเผยให้เขาเห็นในชุมชน Stack Overflow ทั้งหมด

แต่อย่างไร ฉันต้องการแผน แน่ใจว่ามันดูเหมือนตัวเลข แต่ฉันรู้ว่ามันไม่ใช่มีวิธีที่จะพิสูจน์ได้ว่า แค่นั้นแหละ! มันมีลักษณะเช่นจำนวน แต่มันสามารถทำหน้าที่เช่นเดียว?

ฉันบอกว่าfiveฉันต้องการให้เขาใหญ่ขึ้น 5 เท่า - เขาไม่ได้ถามทำไมและฉันไม่ได้อธิบาย ฉันทำสิ่งที่โปรแกรมเมอร์ดี ๆ จะทำ: ฉันทวีคูณ แน่นอนว่าไม่มีทางที่เขาจะปลอมแปลงออกจากสิ่งนี้ได้

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

บ้ามัน! ไม่เพียงfiveแค่ทวีคูณwtfก็ยังอยู่ที่นั่น ประณามชายผู้นี้และมันฝรั่งของเขา

เกิดอะไรขึ้น? ฉันผิดเกี่ยวกับเรื่องทั้งหมดนี้เหรอ? คือfiveจริงๆเป็นจำนวนมาก? ไม่ฉันต้องคิดถึงบางสิ่งบางอย่างฉันรู้ว่ามีบางสิ่งที่ฉันต้องลืมบางสิ่งที่เรียบง่ายและเรียบง่ายที่ฉันมองข้ามไป

นี่ไม่ได้ดูดีเลยฉันเขียนคำตอบนี้หลายชั่วโมงและฉันก็ยังไม่เข้าใกล้ประเด็น ฉันไม่สามารถติดตามเรื่องนี้ได้ในที่สุดผู้คนจะหยุดอ่านฉันต้องคิดถึงบางสิ่งและฉันต้องคิดให้เร็ว

เดี๋ยวก่อน! fiveไม่ใช่ 25, 25 คือผลลัพธ์ 25 เป็นตัวเลขที่แตกต่างอย่างสิ้นเชิง แน่นอนฉันจะลืมได้อย่างไร ตัวเลขไม่เปลี่ยนรูป เมื่อคุณคูณไม่มีอะไรได้รับมอบหมายให้สิ่งที่คุณเพียงแค่สร้างหมายเลขใหม่5 * 525

นั่นต้องเป็นสิ่งที่เกิดขึ้นที่นี่ อย่างใดเมื่อฉันคูณfive * 5, fiveจะต้องได้รับการบังคับให้จำนวนและตัวเลขที่ต้องเป็นหนึ่งที่ใช้สำหรับการคูณ มันเป็นผลลัพธ์ของการคูณที่ได้รับการพิมพ์ไปยังคอนโซลไม่ใช่คุณค่าของfiveตัวมันเอง fiveไม่เคยได้รับมอบหมายอะไรเลยแน่นอนว่ามันจะไม่เปลี่ยนแปลง

ดังนั้นฉันfiveจะกำหนดผลของการผ่าตัดได้อย่างไร ฉันเข้าใจแล้ว. fiveแม้กระทั่งก่อนที่จะมีโอกาสคิดผมก็ตะโกน "++"

> five++
5

Aha! ฉันมีเขา! ทุกคนรู้5 + 1คือ6นี่เป็นหลักฐานที่ฉันต้องการที่จะเปิดเผยว่าfiveไม่ได้เป็นจำนวนมาก! มันเป็นนักต้มตุ๋น! นักต้มตุ๋นที่ไม่ดีที่ไม่รู้วิธีนับ และฉันสามารถพิสูจน์ได้ นี่คือวิธีการทำงานของจำนวนจริง:

> num = 5
5
> num++
5

รอ? เกิดอะไรขึ้นที่นี่? ถอนหายใจฉันรู้สึกอึดอัดใจจนfiveลืมว่าผู้ให้บริการโพสต์ทำงานอย่างไร เมื่อผมใช้++ในตอนท้ายของฉันบอกว่าผลตอบแทนคุ้มค่าในปัจจุบันเพิ่มขึ้นแล้วfive fiveมันคือค่าก่อนที่จะมีการดำเนินการที่ได้รับการพิมพ์ไปยังคอนโซล numเป็นจริง6และฉันสามารถพิสูจน์ได้:

>num
6

ได้เวลาดูสิ่งที่fiveเป็นจริง:

>five
6

... มันเป็นสิ่งที่ควรจะเป็น fiveดี - แต่ฉันดีกว่า หากfiveยังคงเป็นวัตถุที่จะหมายความว่ามันจะยังคงมีคุณสมบัติwtfและฉันก็ยินดีที่จะเดิมพันทุกอย่างที่มันไม่ได้

> five.wtf
undefined

Aha! ฉันถูก. ฉันมีเขา! fiveตอนนี้เป็นจำนวน - มันไม่ใช่วัตถุอีกต่อไป ฉันรู้ว่าเทคนิคการคูณจะไม่ช่วยในครั้งนี้ ดูมันfive++ five = five + 1ซึ่งแตกต่างจากการคูณที่ผู้ประกอบการกำหนดค่าให้กับ++ fiveโดยเฉพาะอย่างยิ่งจะกำหนดผลของการfive + 1ที่เช่นเดียวกับในกรณีของการคูณผลตอบแทนไม่เปลี่ยนรูปใหม่จำนวน

ฉันรู้ว่าฉันมีเขาและเพียงเพื่อให้แน่ใจว่าเขาไม่สามารถดิ้นรนออกจากมัน ฉันได้ทดสอบแขนเสื้อของฉันอีกครั้ง ถ้าฉันพูดถูกและfiveเป็นจำนวนมากตอนนี้สิ่งนี้จะไม่ทำงาน:

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

เขาจะไม่หลอกฉันในครั้งนี้ ฉันรู้ว่าpotato?กำลังจะถูกพิมพ์ไปที่คอนโซลเพราะนั่นเป็นผลลัพธ์ของการมอบหมาย คำถามที่แท้จริงคือจะwtfยังคงอยู่หรือไม่

> five.wtf
undefined

อย่างที่ฉันสงสัย - ไม่มี - เพราะตัวเลขไม่สามารถกำหนดคุณสมบัติได้ เราเรียนรู้ว่าปีแรกที่สถาบันการศึกษา;)

ขอบคุณนาธาน ต้องขอบคุณความกล้าของคุณในการถามคำถามนี้ในที่สุดฉันก็สามารถนำเรื่องทั้งหมดนี้มาไว้ข้างหลังฉันและไปยังคดีใหม่

toValue()เหมือนหนึ่งที่เกี่ยวกับฟังก์ชั่นนี้ โอ้พระเจ้าที่รัก Nooo!


9
ลืมมันไปเถอะ Jake; มันคือ Javascript
เซท

ทำไมเราถึงเรียกฟังก์ชั่นคอนสตรัคเตอร์ "คลาส" ใน 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

01ประกาศฟังก์ชั่นdisที่ส่งกลับวัตถุบริบท สิ่งที่thisแสดงถึงการเปลี่ยนแปลงขึ้นอยู่กับว่าคุณกำลังใช้โหมดเข้มงวดหรือไม่ ตัวอย่างทั้งหมดมีผลลัพธ์ที่แตกต่างกันหากฟังก์ชันถูกประกาศเป็น:

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

นี่คือรายละเอียดในส่วน 10.4.3 ในข้อกำหนด ES5

  1. ถ้ารหัสฟังก์ชันเป็นรหัสที่เข้มงวดให้ตั้งค่า ThisBinding เป็น thisArg
  2. มิฉะนั้นถ้า thisArg เป็นโมฆะหรือไม่ได้กำหนดเซ็ตนี้การเชื่อมโยงไปยังวัตถุทั่วโลก
  3. มิฉะนั้นถ้า Type (thisArg) ไม่ใช่ Object ให้ตั้งค่า ThisBinding เป็น ToObject (thisArg)

02คือค่าส่งคืนของการประกาศฟังก์ชัน undefinedควรอธิบายตนเองได้ที่นี่

03ตัวแปรfiveจะเริ่มต้นกับค่าตอบแทนของเมื่อเรียกในบริบทของมูลค่าดั้งเดิมdis 5เพราะไม่ได้อยู่ในโหมดที่เข้มงวดบรรทัดนี้เป็นเหมือนการโทรdisfive = Object(5)

04Number {[[PrimitiveValue]]: 5}ค่าส่งคืนคี่คือการแสดงวัตถุที่ล้อมรอบค่าดั้งเดิม5

05fiveวัตถุwtfสถานที่ให้บริการที่ได้รับมอบหมายค่าสตริงของ'potato'

06 คือมูลค่าส่งคืนของการมอบหมายและควรอธิบายด้วยตนเอง

07fiveวัตถุwtfสถานที่ให้บริการจะถูกตรวจสอบ

08ตามที่five.wtfได้ตั้งค่าไว้ก่อนหน้านี้เพื่อ'potato'ส่งคืน'potato'ที่นี่

09วัตถุจะถูกคูณด้วยค่าดั้งเดิมfive 5นี้ไม่แตกต่างจากวัตถุอื่น ๆ ที่ได้รับการคูณและมีการอธิบายในส่วน 11.5 ของข้อกำหนด สิ่งที่ควรสังเกตอย่างยิ่งคือการที่วัตถุถูกโยนไปเป็นค่าตัวเลขซึ่งครอบคลุมในบางส่วน

9.3 จำนวน :

  1. ให้ primValue เป็น ToPrimitive (อาร์กิวเมนต์อินพุตหมายเลขคำใบ้)
  2. กลับสู่หมายเลข (primValue)

9.1 ToPrimitive :

ส่งคืนค่าเริ่มต้นสำหรับวัตถุ ค่าเริ่มต้นของวัตถุจะถูกดึงโดยเรียกวิธีการภายใน [[DefaultValue]] ของวัตถุผ่านคำแนะนำทางเลือก PreferredType พฤติกรรมของ [[DefaultValue]] วิธีการภายในจะถูกกำหนดโดยข้อกำหนดนี้สำหรับทุก ECMAScript พื้นเมืองของวัตถุใน8.12.8

8.12.8 [[ค่าเริ่มต้น]] :

ให้ valueOf เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Get]] ของ object O พร้อมอาร์กิวเมนต์ "valueOf"

  1. ถ้า IsCallable (valueOf) เป็นจริง

    1. ให้ val เป็นผลลัพธ์ของการเรียกเมธอด internal [[Call]] ของ valueOf โดยที่ O เป็นค่านี้และรายการอาร์กิวเมนต์ว่าง
    2. ถ้า val เป็นค่าดั้งเดิมส่งคืน val

นี่คือวิธีการวงเวียนทั้งหมดของการบอกว่าvalueOfฟังก์ชั่นของวัตถุที่ได้รับการเรียกและค่าตอบแทนจากฟังก์ชั่นที่ใช้ในสมการ หากคุณต้องการเปลี่ยนvalueOfฟังก์ชั่นคุณสามารถเปลี่ยนผลลัพธ์ของการทำงานได้:

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

10เนื่องจากฟังก์ชั่นfives valueOfไม่เปลี่ยนแปลงมันส่งคืนค่าดั้งเดิมที่ถูกพันไว้5เพื่อfive * 5ประเมินว่า5 * 5ผลลัพธ์ใด25

11fiveวัตถุwtfสถานที่ให้บริการมีการประเมินอีกครั้ง05แม้จะมีการเปลี่ยนแปลงไปจากเมื่อมันได้รับมอบหมายใน

12 'potato'

13Postfix เพิ่มผู้ประกอบการถูกเรียกบนfiveที่ได้รับค่าตัวเลข ( 5เราครอบคลุมวิธีการก่อนหน้า), ร้านค้ามูลค่าเพื่อที่จะสามารถกลับเพิ่ม1ถึงมูลค่า ( 6) กำหนดค่าfiveและผลตอบแทนที่คุ้มค่าที่เก็บไว้ ( 5)

14 ก่อนหน้านี้ค่าที่ส่งคืนคือค่าก่อนที่จะเพิ่มขึ้น

15wtfคุณสมบัติของมูลค่าดั้งเดิม ( 6) เก็บไว้ที่ตัวแปรfiveที่มีการเข้าถึง ส่วนที่ 15.7.5 ของข้อกำหนด ES5กำหนดพฤติกรรมนี้ Number.prototypeเบอร์ได้รับคุณสมบัติจาก

16 Number.prototypeไม่มีwtfคุณสมบัติจึงundefinedถูกส่งกลับ

17 five.wtf'potato?'มีการกำหนดค่าของ การมอบหมายงานที่กำหนดไว้ใน 11.13.1 ของข้อกำหนด โดยทั่วไปค่าที่กำหนดจะถูกส่งคืน แต่ไม่ได้เก็บไว้

18 'potato?' ถูกส่งคืนโดยผู้ดำเนินการที่ได้รับมอบหมาย

19อีกครั้งfiveซึ่งมีค่า6ถูกเข้าถึงและอีกครั้งNumber.prototypeไม่มีwtfคุณสมบัติ

20 undefined ตามที่อธิบายไว้ข้างต้น

21 five ถูกเข้าถึง

22 6 ถูกส่งคืนตามที่อธิบายใน 13


17

มันค่อนข้างง่าย

function dis () { return this; }

สิ่งนี้จะคืนค่าthisบริบท ดังนั้นถ้าคุณทำcall(5)คุณส่งตัวเลขเป็นวัตถุ

ฟังก์ชั่นไม่ได้จัดหาอาร์กิวเมนต์อาร์กิวเมนต์แรกคุณให้เป็นบริบทของcall thisโดยปกติถ้าคุณต้องการให้มันอยู่ในบริบทคุณให้มัน{}ดังนั้นdis.call({})ซึ่งหมายความว่าในการทำงานเป็นที่ว่างเปล่าthis thisอย่างไรก็ตามถ้าคุณผ่าน5มันดูเหมือนว่ามันจะถูกแปลงเป็นวัตถุ ดู. call

ดังนั้นผลตอบแทนคือ object

เมื่อคุณทำfive * 5, JavaScript เห็นวัตถุเป็นชนิดดั้งเดิมเพื่อให้เทียบเท่ากับfive 5 * 5ที่น่าสนใจ'5' * 5ก็คือมันยังคงเท่ากับ25ดังนั้น JavaScript หล่ออย่างชัดเจนภายใต้ประทุน ไม่มีการเปลี่ยนแปลงfiveประเภทพื้นฐานที่ทำในบรรทัดนี้

แต่เมื่อคุณทำ++มันจะแปลงวัตถุเป็นnumberชนิดดั้งเดิมจึงลบ.wtfคุณสมบัติ เพราะคุณกำลังส่งผลกระทบต่อประเภทพื้นฐาน


การกลับมาเป็นจำนวน
GSerg

++แปลงและกำหนดกลับ จะมีค่าเท่ากับ++ variable = variable + 1ดังนั้นคุณคลายตัวwtf
Rajesh

*เทียบ++ไม่ได้สร้างความสับสนให้ฉันเลย การมีฟังก์ชั่นที่คาดว่าจะไม่มีข้อโต้แย้งและคืนค่าthisการมีฟังก์ชั่นนั้นยอมรับการโต้เถียงและส่งคืนบางสิ่งที่เป็นเหมือน แต่ไม่ใช่ข้อโต้แย้งที่แน่นอนซึ่งไม่สมเหตุสมผลสำหรับฉัน
นาธานลอง

อัปเดต @NathanLong เพื่อแสดงว่าdis.call()ทำอะไร
Callum Linington

5 ++ มีพฤติกรรมเหมือนกับในภาษา C จึงไม่มีอะไรน่าแปลกใจ thisเป็นเพียงตัวชี้ไปยังวัตถุดังนั้นประเภทดั้งเดิมจะถูกแปลงโดยปริยาย ทำไมฟังก์ชั่นที่ไม่มีข้อโต้แย้งไม่สามารถมีบริบทได้อย่างน้อย? อาร์กิวเมนต์ที่ 1 ของcallหรือbindถูกใช้เพื่อตั้งค่าบริบท นอกจากนี้ฟังก์ชั่นคือการปิดซึ่งหมายความว่าพวกเขาสามารถเข้าถึงได้มากกว่าเพียงแค่arguments
Rivenfall

10

ค่าดั้งเดิมไม่สามารถมีคุณสมบัติได้ แต่เมื่อคุณพยายามเข้าถึงคุณสมบัติด้วยค่าดั้งเดิมมันจะเปลี่ยนรูปแบบเป็นวัตถุหมายเลขชั่วคราวอย่างโปร่งใส

ดังนั้น:

> 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

disฟังก์ชั่นประกาศ ฟังก์ชันคืนค่าบริบท

function dis() { return this }
undefined

โทรกับบริบทdis 5ค่าดั้งเดิมจะถูกบรรจุเมื่อส่งผ่านเป็นบริบทในโหมดเข้มงวด ( MDN ) ดังนั้นfiveตอนนี้คือวัตถุ (หมายเลขกล่อง)

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

ประกาศwtfคุณสมบัติบนfiveตัวแปร

five.wtf = 'potato'
"potato"

มูลค่าของ five.wtf

five.wtf
"potato"

fiveจะถูกบรรจุอยู่ในกล่อง5ดังนั้นมันจึงเป็นตัวเลขและวัตถุในเวลาเดียวกัน (5 * 5 = 25) fiveมันไม่ได้เปลี่ยนแปลง

five * 5
25

มูลค่าของ five.wtf

five.wtf
"potato"

Unboxing fiveที่นี่ ตอนนี้เป็นเพียงแค่ดั้งเดิมfive numberมันพิมพ์5แล้วเพิ่มไป1five

five++
5

fiveเป็นหมายเลขดั้งเดิม6ตอนนี้ไม่มีคุณสมบัติอยู่ในนั้น

five.wtf
undefined

primitives ไม่มีคุณสมบัติคุณไม่สามารถตั้งค่านี้ได้

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

คุณไม่สามารถอ่านสิ่งนี้ได้เนื่องจากยังไม่ได้ตั้งค่า

five.wtf
undefined

fiveเป็น6เพราะการโพสต์ที่เพิ่มขึ้นข้างต้น

five
6

7

ก่อนอื่นมันดูเหมือนว่าสิ่งนี้กำลังถูกเรียกใช้ผ่านคอนโซล nodejs

1

    function dis() { return this }

สร้างฟังก์ชั่น dis () แต่เพราะมันไม่ได้ถูกตั้งค่าเนื่องจากvarไม่มีค่าที่จะส่งกลับดังนั้นundefinedผลลัพธ์dis()ก็คือแม้ว่าจะถูกกำหนดไว้ บน sidenote thisจะไม่ถูกส่งคืนเนื่องจากฟังก์ชันไม่ได้ถูกดำเนินการ

2

    five = dis.call(5)

ส่งคืนNumberออบเจ็กต์ของจาวาสคริปต์เพราะคุณเพิ่งตั้งค่าฟังก์ชั่นให้dis()เป็นthisค่าดั้งเดิม

3

   five.wtf = 'potato'

ผลตอบแทนที่แรก"potato"เพราะคุณเพียงแค่ตั้งค่าคุณสมบัติwtfของการfive จาวาสคริปต์ที่ส่งกลับค่าของตัวแปรที่คุณตั้งค่าที่ทำให้ง่ายต่อการหลายตัวแปรโซ่และกำหนดให้ค่าเดียวกันเช่นนี้'potato'a = b = c = 2

4

    five * 5

ผลตอบแทนที่นี้25เพราะคุณเพียงแค่คูณจำนวนดั้งเดิมไป5 fiveค่าของfiveถูกกำหนดโดยค่าของNumberวัตถุ

5

    five.wtf

ฉันข้ามบรรทัดนี้มาก่อนเพราะฉันจะทำซ้ำที่นี่ มันแค่คืนค่าของคุณสมบัติwtfที่คุณตั้งไว้ด้านบน

6

    five++

ในฐานะที่เป็น @Callum กล่าวว่า++จะแปลงประเภทที่จะจากมูลค่าเดียวกันจากวัตถุnumberNumber {[[PrimitiveValue]]: 5}}

เนื่องจากfiveเป็นnumber, คุณไม่สามารถตั้งค่าคุณสมบัติได้อีกต่อไปจนกว่าคุณจะทำสิ่งนี้:

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

หรือ

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

นอกจากนี้โปรดทราบว่าวิธีที่สองจะมีพฤติกรรมที่แตกต่างจากการใช้วิธีแรกเนื่องจากกำหนดวัตถุทั่วไปแทนNumberวัตถุที่สร้างขึ้นก่อนหน้านี้

ฉันหวังว่านี้จะช่วยให้จาวาสคริปต์ชอบที่จะคิดสิ่งเพื่อที่จะสามารถได้รับสับสนเมื่อมีการเปลี่ยนรอบจากวัตถุโบราณNumber numberคุณสามารถตรวจสอบสิ่งที่เป็นประเภทโดยใช้typeofคำหลักเขียน typeof ห้าหลังจากคุณเริ่มต้นมันกลับ'object'มาและหลังจากที่คุณfive++กลับ'number'มา

@deceze อธิบายความแตกต่างระหว่างจำนวนวัตถุและจำนวนดั้งเดิมได้ดีมาก


6

ขอบเขต JavaScript ทำจาก Execution Contexts บริบทการดำเนินการแต่ละรายการมีสภาพแวดล้อมคำศัพท์ (ค่าที่กำหนดขอบเขตภายนอก / ทั่วโลก), สภาพแวดล้อมที่เปลี่ยนแปลงได้ (ค่าที่กำหนดขอบเขตแบบโลคัล) และการเชื่อมโยงนี้นี้

การรวมนี้เป็นส่วนสำคัญของบริบทการดำเนินการ การใช้callเป็นวิธีหนึ่งในการเปลี่ยนแปลงการเชื่อมโยงนี้และการทำเช่นนั้นจะสร้างวัตถุเพื่อเติมการเชื่อมโยงโดยอัตโนมัติ

Function.prototype.call () (จาก MDN)

วากยสัมพันธ์
fun.call(thisArg[, arg1[, arg2[, ...]]])

thisArg
ค่าของสิ่งนี้จัดไว้สำหรับการโทรเพื่อความสนุกสนาน ทราบว่านี้อาจจะไม่คุ้มค่าที่เกิดขึ้นจริงเห็นได้โดยวิธีการ: ถ้าวิธีการที่เป็นฟังก์ชั่นที่ไม่เข้มงวดรหัสโหมดโมฆะและไม่ได้กำหนดจะถูกแทนที่ด้วยวัตถุโลกและค่าดั้งเดิมจะถูกแปลงเป็นวัตถุ (เน้นที่เหมือง)

เมื่อเห็นได้ชัดว่า 5 กำลังถูกแปลงเป็นnew Number(5)ส่วนที่เหลือควรจะค่อนข้างชัดเจน โปรดทราบว่าตัวอย่างอื่นจะใช้งานได้ตราบใดที่ยังเป็นค่าดั้งเดิม

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" />

ป้อนคำอธิบายรูปภาพที่นี่


4

แนวคิดสองสามข้ออธิบายว่าเกิดอะไรขึ้น

5 เป็นตัวเลขค่าดั้งเดิม

Number {[[PrimitiveValue]]: 5} เป็นตัวอย่างของ Number (เรียกว่าเป็นวัตถุห่อหุ้ม)

เมื่อใดก็ตามที่คุณเข้าถึงคุณสมบัติ / วิธีการเกี่ยวกับค่าดั้งเดิมเครื่องยนต์ JS จะสร้างเสื้อคลุมวัตถุประเภทที่เหมาะสม ( Numberสำหรับ5, Stringสำหรับ'str'และBooleanสำหรับtrue) และแก้ไขปัญหาการเข้าถึงสถานที่ให้บริการโทร / วิธีการบนวัตถุที่ห่อหุ้ม นี่คือสิ่งที่เกิดขึ้นเมื่อคุณทำtrue.toString()เช่น

เมื่อทำการดำเนินการกับวัตถุพวกมันจะถูกแปลงเป็นค่าดั้งเดิม (โดยใช้toStringหรือvalueOf) เพื่อแก้ไขการดำเนินการเหล่านั้น - ตัวอย่างเช่นเมื่อทำ

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

stringจะถือ concatenation สตริงmystrและobj.toString()และnumberจะถือการเพิ่มของและ3obj.valueOf()

ตอนนี้ที่จะนำมันมารวมกัน

five = dis.call(5)

dis.call(5)ทำงานเหมือน(5).dis()ว่า5มีวิธีการdisจริง เพื่อแก้ไขการเรียกใช้เมธอดตัวสร้างออบเจ็กต์จะถูกสร้างขึ้นและการเรียกเมธอดจะถูกแก้ไข ณ จุดนี้ห้าจุดไปที่ wrapper วัตถุรอบค่าดั้งเดิม 5

five.wtf = 'potato'

การตั้งค่าคุณสมบัติบนวัตถุไม่มีอะไรแฟนซีที่นี่

five * 5

นี่คือfive.valueOf() * 5การรับค่าดั้งเดิมจาก wrapper วัตถุ fiveยังคงชี้ไปที่วัตถุเริ่มต้น

five++

five = five.valueOf() + 1นี้เป็นจริง ก่อนที่จะสายนี้fiveถือกระดาษห่อวัตถุรอบ ๆ มูลค่า 5 ในขณะที่หลังจากจุดนี้five6ถือเป็นค่าดั้งเดิม

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

fiveไม่ใช่วัตถุอีกต่อไป แต่ละบรรทัดเหล่านั้นสร้างอินสแตนซ์ใหม่ของ Number เพื่อแก้ไขการ.wtfเข้าถึงคุณสมบัติ อินสแตนซ์นั้นเป็นอิสระดังนั้นการตั้งค่าคุณสมบัติหนึ่งจะไม่สามารถมองเห็นได้อีก รหัสเทียบเท่ากับรหัสนี้ทั้งหมด:

(new Number(6)).wtf;
(new Number(6)).wtf = 'potato?';
(new Number(6)).wtf;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.