โอเปอเรเตอร์ JavaScript >>> คืออะไรและคุณใช้งานอย่างไร?


150

ฉันดูโค้ดจาก Mozilla ที่เพิ่มวิธีการกรองลงใน Array และมีบรรทัดของรหัสที่ทำให้ฉันสับสน

var len = this.length >>> 0;

ฉันไม่เคยเห็น >>> ใช้ใน JavaScript มาก่อน
มันคืออะไรและมันทำอะไร?


@CMS จริงรหัส / คำถามนี้มาจากเหล่านั้น อย่างไรก็ตามคำตอบที่นี่มีความเฉพาะเจาะจงและมีค่ามากกว่าที่ผ่านมา
Justin Johnson

2
หรือมันเป็นข้อผิดพลาดหรือพวก Mozilla คาดว่าสิ่งนี้ความยาวอาจเป็น -1 >>> เป็นตัวดำเนินการกะที่ไม่ได้ลงชื่อดังนั้น var len จะเป็น 0 หรือมากกว่าเสมอ
user347594

1
Ash Searle พบว่ามีประโยชน์สำหรับมัน - พลิกคว่ำการใช้งานของ JS (Doug Crockford) เพื่อArray.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (แม้ว่าเขาจะทำแบบทดสอบฮ่าฮ่า)
Dan Beam

คำตอบ:


211

มันไม่เพียงแปลงไม่ใช่ตัวเลขให้เป็นตัวเลข แต่แปลงเป็นตัวเลขที่สามารถแสดงเป็น ints แบบไม่ลงนามแบบ 32 บิต

แม้ว่าตัวเลข JavaScript เป็นลอยแม่นยำสอง (*), ผู้ประกอบการบิต ( <<, >>, &, |และ~) มีการกำหนดไว้ในแง่ของการดำเนินงานในจำนวนเต็ม 32 บิต การดำเนินการ bitwise จะแปลงตัวเลขเป็น int แบบ 32 บิตที่มีการสูญเสียเศษส่วนและบิตที่สูงกว่า 32 ก่อนทำการคำนวณแล้วแปลงกลับเป็น Number

ดังนั้นการดำเนินการระดับบิตโดยไม่มีผลกระทบที่แท้จริงเช่นการเลื่อนไปทางขวาของ 0 บิต>>0เป็นวิธีที่รวดเร็วในการปัดเศษตัวเลขและให้แน่ใจว่าอยู่ในช่วง int 32- บิต นอกจากนี้ตัว>>>ดำเนินการสามตัวหลังจากทำการดำเนินการที่ไม่ได้ลงชื่อแปลงผลลัพธ์ของการคำนวณเป็น Number เป็นจำนวนเต็มที่ไม่ได้ลงนามแทนที่จะเป็นจำนวนเต็มที่มีเครื่องหมายที่คนอื่นทำดังนั้นจึงสามารถใช้ในการแปลงเนกาทีฟให้เป็น 32 บิต รุ่นเป็นจำนวนมาก การใช้>>>0ให้แน่ใจว่าคุณมีจำนวนเต็มระหว่าง 0 ถึง 0xFFFFFFFF

ในกรณีนี้สิ่งนี้มีประโยชน์เพราะ ECMAScript กำหนดดัชนี Array ในรูปของ 32 บิตที่ไม่ได้ลงนาม ints ดังนั้นหากคุณกำลังพยายามที่จะนำไปใช้array.filterในลักษณะที่ซ้ำกับที่มาตรฐาน ECMAScript Fifth Edition บอกเอาไว้อย่างแน่นอนคุณจะต้องใช้ตัวเลข 32- บิตแบบไม่ลงนามแบบนี้

(ในความเป็นจริงมีความจำเป็นในการปฏิบัติเล็ก ๆ น้อย ๆ นี้เช่นหวังว่าคนจะไม่ได้ไปจะได้รับการตั้งค่าarray.lengthเพื่อ0.5, -1, 1e21หรือ'LEMONS'. แต่นี้เป็นผู้เขียน JavaScript เรากำลังพูดถึงเพื่อให้คุณไม่เคยรู้ ... )

สรุป:

1>>>0            === 1
-1>>>0           === 0xFFFFFFFF          -1>>0    === -1
1.7>>>0          === 1
0x100000002>>>0  === 2
1e21>>>0         === 0xDEA00000          1e21>>0  === -0x21600000
Infinity>>>0     === 0
NaN>>>0          === 0
null>>>0         === 0
'1'>>>0          === 1
'x'>>>0          === 0
Object>>>0       === 0

(*: พวกมันถูกนิยามว่าทำตัวเหมือนลอย ๆ มันจะไม่แปลกใจเลยถ้าเครื่องมือ JavaScript บางอันใช้ ints จริง ๆ เท่าที่จะทำได้ด้วยเหตุผลด้านประสิทธิภาพ แต่นั่นจะเป็นรายละเอียดของการใช้งาน ประโยชน์จาก.)


2
2 ในรายละเอียดเชิงลึกและตาราง -1 เพราะ array.length ตรวจสอบตัวเองและไม่สามารถตั้งค่าได้โดยพลการอะไรที่ไม่เป็นจำนวนเต็มหรือ 0 (FF โยนข้อผิดพลาดนี้: RangeError: invalid array length)
Justin Johnson

4
อย่างไรก็ตามข้อมูลจำเพาะจงใจอนุญาตให้ฟังก์ชั่น Array จำนวนมากถูกเรียกใช้บน non-Array (เช่นผ่านArray.prototype.filter.call) ดังนั้นจึงarrayอาจไม่ใช่ของจริงArray: มันอาจเป็นคลาสที่ผู้ใช้กำหนดอื่น ๆ (น่าเสียดายที่มันไม่สามารถเป็น NodeList ได้อย่างแน่นอนซึ่งเป็นเวลาที่คุณต้องการทำเช่นนั้นเพราะเป็นวัตถุโฮสต์ที่ออกจากสถานที่เดียวที่คุณจะทำแบบเสมือนจริงในแบบจำลองargumentsหลอก)
Bobince

คำอธิบายที่ยอดเยี่ยมและตัวอย่างที่ยอดเยี่ยม! น่าเสียดายที่นี่เป็นอีกแง่มุมหนึ่งของ Javascript ฉันแค่ไม่เข้าใจว่ามีอะไรที่น่ากลัวมากในการขว้างข้อผิดพลาดเมื่อคุณได้รับแบบผิด ๆ เป็นไปได้ที่จะอนุญาตให้พิมพ์แบบไดนามิกโดยไม่ให้เกิดข้อผิดพลาดโดยไม่ตั้งใจทุกครั้งเพื่อสร้างการคัดเลือกนักแสดง :(
Mike Williamson

"การใช้ >>> 0 ช่วยให้คุณมั่นใจว่าคุณมีจำนวนเต็มระหว่าง 0 ถึง 0xFFFFFFFF" สิ่งที่จะifดูคำสั่งเหมือนนี้เมื่อพยายามที่จะระบุว่าด้านซ้ายของการประเมินผลไม่ได้เป็น int? 'lemons'>>>0 === 0 && 0 >>>0 === 0ประเมินว่าเป็นความจริง? แม้ว่ามะนาวจะเป็นคำที่เห็นได้ชัด ..
Zze

58

ผู้ประกอบการได้รับการรับรองการเปลี่ยนแปลงทางด้านขวาจะใช้ในทุกเสริมอาร์เรย์ของวิธีการใช้งานของ Mozilla เพื่อให้แน่ใจว่าlengthสถานที่ให้บริการเป็นที่ไม่ได้ลงชื่อ 32 บิตจำนวนเต็ม

lengthคุณสมบัติของวัตถุอาร์เรย์ที่อธิบายไว้ในสเปคเป็น:

วัตถุ Array ทุกชิ้นมีคุณสมบัติความยาวซึ่งค่าจะเป็นจำนวนเต็มที่ไม่ใช่ค่าลบน้อยกว่า 2 32เสมอ

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

การปรับใช้อาร์เรย์พิเศษของ Mozilla พยายามที่จะเป็นไปตามมาตรฐานECMAScript 5ดูคำอธิบายของArray.prototype.indexOfวิธีการ (§ 15.4.4.14):

1. ให้ O เป็นผลลัพธ์ของการเรียก ToObject ผ่านค่านี้ 
   เป็นอาร์กิวเมนต์
2. ให้ lenValue เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Get]] ของ O ด้วย 
   อาร์กิวเมนต์ "ความยาว"
3. ให้ len จะToUint32 (lenValue)
....

ที่คุณสามารถดูพวกเขาเพียงต้องการที่จะทำซ้ำพฤติกรรมของToUint32วิธีการเพื่อให้สอดคล้องกับข้อมูลจำเพาะ ES5 ในการดำเนินการ ES3 และที่ผมกล่าวว่าก่อนที่ผู้ประกอบการได้รับการรับรองการเปลี่ยนแปลงทางด้านขวาเป็นวิธีที่ง่าย


ในขณะที่การใช้งานอาร์เรย์พิเศษที่เชื่อมโยงอาจถูกต้อง (หรือปิดเพื่อแก้ไข) รหัสยังคงเป็นตัวอย่างรหัสที่ไม่ดี บางทีแม้แต่ความคิดเห็นเพื่อชี้แจงความตั้งใจจะแก้ไขสถานการณ์นี้
fmark

2
เป็นไปได้ไหมที่ความยาวของอาร์เรย์ไม่ใช่จำนวนเต็ม? ฉันไม่สามารถจินตนาการได้ดังนั้นแบบนี้ToUint32ดูเหมือนจะไม่จำเป็นสำหรับฉัน
Marcel Korpel

7
@Marcel: โปรดจำไว้ว่าวิธีการส่วนใหญ่เป็นArray.prototypeวิธีที่ใช้กันทั่วไปโดยเจตนาสามารถใช้กับวัตถุที่มีลักษณะคล้ายอาร์เรย์Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;ได้ argumentsวัตถุยังเป็นตัวอย่างที่ดี สำหรับออบเจ็กต์อาร์เรย์บริสุทธิ์เป็นไปไม่ได้ที่จะเปลี่ยนประเภทของlengthคุณสมบัติเนื่องจากจะใช้วิธีการภายใน[[Put ]] พิเศษและเมื่อมีการมอบหมายงานให้กับlengthคุณสมบัติแล้วจะถูกแปลงอีกครั้งToUint32และดำเนินการอื่น ๆ เช่นการลบดัชนีด้านบน ความยาวใหม่ ...
CMS

32

นั่นคือโอเปอเรเตอร์การเปลี่ยนบิตที่ไม่ได้ลงนาม ความแตกต่างระหว่างสิ่งนี้และตัวดำเนินการเปลี่ยนบิตด้านขวาที่ลงนามคือตัวดำเนินการเปลี่ยนบิตขวาที่ไม่ได้ลงชื่อ ( >>> ) เติมด้วยศูนย์จากด้านซ้ายและตัวดำเนินการเปลี่ยนบิตด้านขวาที่ลงนาม ( >> ) เติมด้วยบิตบิต การรักษาสัญลักษณ์ของค่าตัวเลขเมื่อเลื่อน


อีวานนั่นจะเปลี่ยนเป็น 0 แห่ง คำสั่งนั้นจะไม่เปลี่ยนแปลงอะไรเลย
คณบดี J

3
@Ivan ปกติแล้วฉันจะบอกว่าการเปลี่ยนค่าของศูนย์โดยไม่มีเหตุผล แต่นี่คือ Javascript ดังนั้นอาจมีความหมายอยู่เบื้องหลัง ฉันไม่ใช่กูรูของ Javascript แต่อาจเป็นวิธีที่ทำให้มั่นใจได้ว่าค่านั้นเป็นจำนวนเต็มในภาษา Javasacript ที่ไม่สามารถพิมพ์ได้
driis

2
@Ivan ดูคำตอบของ Justin ด้านล่าง ในความเป็นจริงเป็นวิธีที่จะทำให้แน่ใจว่าตัวแปร len มีตัวเลข
driis

1
นอกจากนี้>>>แปลงเป็นจำนวนเต็มซึ่งเอกภาพ+ไม่ได้ทำ
เรียกซ้ำเมื่อ

this.length >>> 0 แปลงเลขจำนวนเต็มที่ลงนามเป็นเลขที่ไม่ได้ลงนาม โดยส่วนตัวฉันพบว่ามีประโยชน์เมื่อโหลดไฟล์ไบนารีด้วย int ที่ไม่ได้ลงนามในนั้น
Matt Parkins

29

Driisได้อธิบายอย่างเพียงพอว่าตัวดำเนินการคืออะไรและทำงานอย่างไร นี่คือความหมายที่อยู่เบื้องหลัง / ทำไมจึงถูกใช้:

ขยับทิศทางใดโดย0ไม่ส่งกลับจำนวนเดิมและจะเหวี่ยงไปnull 0ดูเหมือนว่าโค้ดตัวอย่างที่คุณกำลังดูอยู่นั้นใช้this.length >>> 0เพื่อให้แน่ใจว่าlenเป็นตัวเลขแม้ว่าthis.lengthจะไม่ได้กำหนดไว้

สำหรับคนจำนวนมากการใช้งานระดับบิตไม่ชัดเจน (และ Douglas Crockford / jslint แนะนำว่าไม่ควรใช้สิ่งเหล่านี้) ไม่ได้หมายความว่ามันผิดที่จะทำ แต่มีวิธีการที่ดีและคุ้นเคยมากกว่าเพื่อให้โค้ดอ่านง่ายขึ้น วิธีที่ชัดเจนมากขึ้นเพื่อให้แน่ใจว่าlenเป็น0อย่างใดอย่างหนึ่งดังต่อไปนี้ทั้งสองวิธี

// Cast this.length to a number
var len = +this.length;

หรือ

// Cast this.length to a number, or use 0 if this.length is
// NaN/undefined (evaluates to false)
var len = +this.length || 0; 

1
แม้ว่าวิธีการแก้ปัญหาที่สองของคุณบางครั้งจะประเมินNaN.. เช่น+{}... มันอาจจะดีที่สุดที่จะรวมทั้งสอง:+length||0
เจมส์

1
this.length อยู่ในบริบทของวัตถุอาร์เรย์ซึ่งไม่สามารถเป็นอะไรนอกจากจำนวนเต็มที่ไม่เป็นลบ (อย่างน้อยใน FF) ดังนั้นจึงไม่สามารถเป็นไปได้ที่นี่ นอกจากนี้ {} || 1 ส่งกลับ {} ดังนั้นคุณจะไม่ดีขึ้นถ้าสิ่งนี้ความยาวเป็นวัตถุ ประโยชน์ในการหล่อแบบนี้คือความยาวในวิธีแรกคือมันจัดการกับกรณีที่ this.length คือ NaN แก้ไขการตอบสนองเพื่อสะท้อนว่า
Justin Johnson

jslint จะบ่นเกี่ยวกับ var len = + this.length เช่นเดียวกับ "plusses ที่สับสน" ดักลาสคุณจู้จี้จุกจิก!
Bayard Randel

ดักลาสเป็นคนพิถีพิถัน และในขณะที่ข้อโต้แย้งของเขาฉลาดและก่อตั้งได้ดี แต่สิ่งที่เขาพูดนั้นไม่สมบูรณ์หรือเป็นข่าวประเสริฐ
Justin Johnson

15

>>>คือไม่ได้ลงนามดำเนินการเปลี่ยนแปลงทางขวา ( ดูหน้า. 76 สเป JavaScript 1.5 ) เมื่อเทียบกับ>>การลงนามดำเนินการเปลี่ยนแปลงทางขวา

>>>การเปลี่ยนแปลงผลของการขยับตัวเลขติดลบเพราะมันไม่ได้รักษาบิตเครื่องหมายเมื่อขยับ ผลที่ตามมาของสิ่งนี้สามารถเข้าใจได้โดยตัวอย่างจากนักแปล:

$ 1 >> 0
1
$ 0 >> 0
0
$ -1 >> 0
-1
$ 1 >>> 0
1
$ 0 >>> 0
0
$ -1 >>> 0
4294967295
$(-1 >>> 0).toString(16)
"ffffffff"
$ "cabbage" >>> 0
0

ดังนั้นสิ่งที่น่าจะเกิดขึ้นที่นี่คือการได้ความยาวหรือ 0 ถ้าความยาวนั้นไม่ได้กำหนดหรือไม่ใช่จำนวนเต็มตาม"cabbage"ตัวอย่างด้านบน ผมคิดว่าในกรณีนี้มันมีความปลอดภัยที่จะสรุปว่าจะไม่this.length < 0อย่างไรก็ตามฉันขอยืนยันว่าตัวอย่างนี้เป็นแฮ็คที่น่ารังเกียจด้วยเหตุผลสองประการ:

  1. พฤติกรรมของ<<<เมื่อใช้ตัวเลขติดลบผลข้างเคียงอาจไม่ได้มีเจตนา (หรือน่าจะเกิดขึ้น) ในตัวอย่างข้างต้น

  2. ความตั้งใจของรหัสไม่ชัดเจนเนื่องจากการมีอยู่ของคำถามนี้จะตรวจสอบ

แนวปฏิบัติที่ดีที่สุดอาจใช้สิ่งที่อ่านได้มากกว่าเว้นแต่ประสิทธิภาพจะมีความสำคัญอย่างยิ่ง:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)

Sooo ... @johncatfish ถูกต้องไหม มันเพื่อให้แน่ใจว่าสิ่งนี้มีความยาวไม่เป็นลบหรือไม่?
Anthony

4
กรณีของการ-1 >>> 0เกิดขึ้นเคยและถ้าเป็นเช่นนั้นมันเป็นที่พึงปรารถนาจริงๆที่จะเปลี่ยนเป็น 4294967295? ดูเหมือนว่าสิ่งนี้จะทำให้ลูปทำงานได้สองสามครั้งเกินความจำเป็น
หลอกลวง

@deceze: ไม่เห็นการใช้งานthis.lengthเป็นไปไม่ได้ที่จะรู้ สำหรับการดำเนินการ "สติ" ใด ๆ ความยาวของสตริงไม่ควรเป็นค่าลบ แต่อย่างใดอย่างหนึ่งอาจโต้แย้งว่าในสภาพแวดล้อม "สติ" เราสามารถสันนิษฐานการมีอยู่ของthis.lengthคุณสมบัติที่มักจะส่งกลับจำนวนหนึ่ง
fmark

คุณบอกว่า >>> ไม่รักษาเครื่องหมายบิต .. ตกลง .. ดังนั้นฉันต้องถามเมื่อเราจัดการกับจำนวนลบ .. ก่อนที่จะมีการแปลง >>> หรือ >> พวกเขาอยู่ในเกณฑ์ 2 วินาที form หรืออยู่ในรูปจำนวนเต็มที่มีลายเซ็นและเราจะรู้ได้อย่างไร โดยวิธีการเสริม 2s ฉันคิดว่าอาจจะไม่ได้รับการบอกว่ามีสัญญาณบิต .. มันเป็นทางเลือกแทนสัญกรณ์ที่ลงนาม แต่มันเป็นไปได้ที่จะตรวจสอบสัญลักษณ์ของจำนวนเต็ม
barlop

10

เหตุผลสองประการ:

  1. ผลลัพธ์ของ >>> คือ "อินทิกรัล"

  2. ไม่ได้กำหนด >>> 0 = 0 (เนื่องจาก JS จะพยายามและบังคับ LFS ให้เป็นบริบทตัวเลขซึ่งจะใช้กับ "foo" >>> 0 เป็นต้น)

จำไว้ว่าตัวเลขใน JS มีการแทนค่าภายในเป็น double มันเป็นเพียงวิธีที่รวดเร็วในการป้อนสติพื้นฐานสำหรับความยาว

อย่างไรก็ตาม -1 >>> 0 (อ๊ะดูเหมือนจะไม่ได้ความยาวที่ต้องการ!)


0

ตัวอย่างโค้ด Java ด้านล่างอธิบายได้ดี:

int x = 64;

System.out.println("x >>> 3 = "  + (x >>> 3));
System.out.println("x >> 3 = "  + (x >> 3));
System.out.println(Integer.toBinaryString(x >>> 3));
System.out.println(Integer.toBinaryString(x >> 3));

เอาท์พุทเป็นดังต่อไปนี้:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.