ไวยากรณ์ JavaScript / jQuery นี้ทำงานอย่างไร: (ฟังก์ชั่น (หน้าต่าง, ไม่ได้กำหนด) {}) (หน้าต่าง)


153

คุณเคยดูที่ซอร์สโค้ดjQuery 1.4และสังเกตว่ามันถูกห่อหุ้มด้วยวิธีดังต่อไปนี้:

(function( window, undefined ) {

  //All the JQuery code here 
  ...

})(window);

ฉันได้อ่านบทความเกี่ยวกับการตั้งชื่อJavaScriptและอีกชื่อหนึ่งเรียกว่า " คู่ที่สำคัญของ Parens " เพื่อให้ฉันรู้เกี่ยวกับสิ่งที่เกิดขึ้นที่นี่

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


3
ฉันต้องการเพิ่มว่า Paul Irish พูดถึงเรื่องนี้ในวิดีโอนี้: paulirish.com/2010/10-things-i-learned-from-the-jquery-source
Mottie

@Bergi คุณทำเครื่องหมายคำถามของฉันว่าซ้ำซ้อน แต่ฉันถามคำถามนั้นเกือบหนึ่งปีก่อนที่จะซ้ำซ้อน นักแสดงควรเป็นวิธีอื่น ๆ
dkinzer

@dkinzer: มันไม่เกี่ยวกับการถูกถามก่อนหน้านี้มันเกี่ยวกับคำตอบที่มีคุณภาพสูงสุด ในกรณีนี้มันเป็นคอและคอ แต่ฉันคิดว่า CMS เป็นคำตอบที่ดีที่สุด มาที่chat.stackoverflow.com/rooms/17เพื่อพูดคุยเรื่องนี้แม้ว่า
Bergi

คำตอบ:


161

undefined = "new value";ไม่ได้กำหนดเป็นตัวแปรตามปกติและสามารถเปลี่ยนแปลงได้เพียงกับ ดังนั้น jQuery จึงสร้างตัวแปร "undefined" แบบโลคอลที่ไม่ได้กำหนดจริงๆ

ตัวแปรหน้าต่างถูกสร้างขึ้นแบบโลคัลด้วยเหตุผลด้านประสิทธิภาพ เนื่องจากเมื่อ JavaScript ค้นหาตัวแปรมันจะผ่านตัวแปรในเครื่องก่อนจนกว่าจะพบชื่อตัวแปร เมื่อไม่พบ JavaScript จะผ่านขอบเขตถัดไปเป็นต้นจนกว่าจะกรองตัวแปรทั่วโลก ดังนั้นหากตัวแปรหน้าต่างถูกสร้างขึ้นแบบท้องถิ่น JavaScript สามารถค้นหาได้เร็วขึ้น ข้อมูลเพิ่มเติม: เร่งความเร็ว JavaScript ของคุณ - Nicholas C. Zakas


1
ขอบคุณ! นั่นเป็นวิดีโอที่ให้ข้อมูลมาก ฉันคิดว่าคุณต้องแก้ไขคำตอบของคุณเพื่อพูดว่า "ตัวแปรหน้าต่างเป็นแบบโลคอล" ฉันคิดว่านี่เป็นคำตอบที่ดีที่สุด ฉันนับและพบว่าวัตถุหน้าต่างถูกเรียกอย่างน้อย 17 ครั้งในซอร์สโค้ด JQuery ดังนั้นจะต้องมีผลกระทบที่สำคัญในการย้ายหน้าต่างเข้าสู่ขอบเขตภายใน
dkinzer

@DKinzer โอ้ใช่คุณกำลัง rght แน่นอนมันทำให้ท้องถิ่น ดีใจที่ฉันสามารถช่วยคุณ =)
Vincent

1
ตามการทดสอบที่ได้รับการอัปเดตนี้jsperf.com/short-scope/5 การส่งผ่าน 'หน้าต่าง' นั้นช้ากว่าจริง ๆ เมื่อมันได้รับค่าคงที่ของหน้าต่างผ่าน jQuery และโดยเฉพาะอย่างยิ่งสำหรับ Safari ดังนั้นในขณะที่ 'ไม่ได้กำหนด' มีกรณีการใช้งานฉันไม่แน่ใจว่า 'หน้าต่าง' มีประโยชน์อย่างยิ่งในทุกกรณี
hexalys

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

2
@ lukas.pukenis เมื่อคุณสร้างห้องสมุดที่ใช้ในเว็บไซต์หลายล้านเว็บไซต์คุณไม่ควรตั้งสมมติฐานว่าคนบ้าจะทำอะไร
JLRishe

54

ไม่ได้กำหนด

โดยการประกาศundefinedเป็นอาร์กิวเมนต์ แต่ไม่เคยส่งค่าไปยังมันเพื่อให้แน่ใจว่ามันจะไม่ได้กำหนดเสมอเพราะมันเป็นเพียงตัวแปรในขอบเขตทั่วโลกที่สามารถเขียนทับ นี่a === undefinedเป็นทางเลือกที่ปลอดภัยtypeof a == 'undefined'ซึ่งช่วยประหยัดอักขระได้ไม่กี่ตัว นอกจากนี้ยังทำให้โค้ดเป็นมิตรกับตัว Minifier มากขึ้นเช่นundefinedสามารถย่อให้สั้นลงได้uซึ่งช่วยประหยัดอักขระได้อีกไม่กี่ตัว

หน้าต่าง

ผ่านwindowเป็นอาร์กิวเมนต์พร้อมสำเนาอยู่ในขอบเขตท้องถิ่นซึ่งมีผลต่อประสิทธิภาพการทำงาน: http://jsperf.com/short-scope การเข้าถึงทั้งหมดwindowจะต้องเดินทางน้อยกว่าหนึ่งระดับของขอบเขตขอบเขต เช่นเดียวกับundefinedการทำสำเนาโลคอลอีกครั้งช่วยลดการทำลายให้มากขึ้น


sidenote:

แม้ว่าสิ่งนี้อาจไม่ได้เป็นความตั้งใจของผู้พัฒนา jQuery แต่การส่งผ่านwindowจะช่วยให้ห้องสมุดสามารถรวมเข้ากับสภาพแวดล้อม Javascript ทางฝั่งเซิร์ฟเวอร์ได้ง่ายขึ้นเช่นnode.jsซึ่งไม่มีwindowวัตถุระดับโลก ในสถานการณ์เช่นนี้จำเป็นต้องเปลี่ยนเพียงหนึ่งบรรทัดเพื่อแทนที่windowวัตถุด้วยอีกบรรทัดหนึ่ง ในกรณีของ jQuery windowวัตถุจำลองสามารถสร้างและส่งผ่านเพื่อวัตถุประสงค์ในการขูด HTML (ไลบรารีเช่นjsdomสามารถทำได้)


2
+1 สำหรับการกล่าวถึง Minification หวังว่าฉันจะได้ +2 สำหรับ jsperf - เวอร์ชั่นย่อคือ(function(a,b){})(window);- aและbสั้นกว่าwindowและมากundefined:)
gnarf

+1 ฉันเคยได้ยินเรื่องขอบเขตโซ่มาก่อน แต่ลืมคำศัพท์ ขอบคุณ!
alex

นอกจากนี้ยังเป็นทางเลือกแทนการใช้ช่องว่าง (0) ซึ่งมีขึ้นเพื่อเหตุผลเดียวกัน: ช่องว่าง (0) เป็นวิธีที่ปลอดภัยในการรับค่าที่ไม่ได้กำหนด
glmxndr

"สิ่งที่คุณพูด" อ้างอิงถึงคำถามอื่น ๆ นี้: stackoverflow.com/questions/4945265/…
gnarf

@gnarf ขอบคุณสำหรับการแจ้งเตือนว่ามีการรวมคำถาม ฉันจะแก้ไขคำตอบของฉันเพื่อให้เข้าใจมากขึ้น;)
David Tang

16

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

สามารถแสดงเหตุผลของการผ่านหน้าต่างด้วยตัวอย่างต่อไปนี้

(function() {
   console.log(window);
   ...
   ...
   ...
   var window = 10;
})();

บันทึกของคอนโซลคืออะไร คุณค่าของwindowวัตถุใช่มั้ย ไม่ถูกต้อง! 10? ไม่ถูกต้อง! undefinedมันบันทึก Javascript interpreter (หรือคอมไพเลอร์ JIT) เขียนใหม่ด้วยวิธีนี้ -

(function() {
   var window; //and every other var in this function

   console.log(window);
   ...
   ...
   ...
   window = 10;

})();

อย่างไรก็ตามหากคุณได้รับwindowตัวแปรเป็นอาร์กิวเมนต์ไม่มี var และดังนั้นจึงไม่น่าประหลาดใจ

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


6

windowถูกส่งผ่านเช่นนั้นในกรณีที่มีคนตัดสินใจที่จะกำหนดวัตถุหน้าต่างใน IE ฉันถือว่าเหมือนกันundefinedในกรณีที่มันได้รับมอบหมายอีกครั้งในภายหลัง

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

นี้อาจจะเป็นเรื่องง่ายที่จะคิดว่าด้วยการแสดงกรณีทั่วไปที่ใช้ใน jQuery ปลั๊กอิน.noConflict()จัดการดังนั้นส่วนใหญ่ของรหัสที่คุณยังคงสามารถใช้$ถึงแม้ว่ามันจะหมายถึงสิ่งอื่น ๆกว่าjQueryนอกขอบเขตนี้:

(function($) {
  //inside here, $ == jQuery, it was passed as the first argument
})(jQuery);

ขอบคุณ มันสมเหตุสมผลมาก แม้ว่าฉันคิดว่าคำตอบคือการรวมกันของสิ่งนี้และสิ่งที่ @ C.Zakas พูด
dkinzer

@DKinzer ฉันกลัวว่าฉันไม่ใช่ C. Zakas;)
Vincent

@Vincent ขอโทษด้วย :-)
dkinzer

5

ทดสอบด้วยการซ้ำ 1000000 การโลคัลไลซ์ชนิดนี้ไม่มีผลกับประสิทธิภาพ ไม่แม้แต่วินาทีเดียวในการทำซ้ำ 1000000 นี่มันไร้ประโยชน์จริงๆ


2
ไม่ต้องพูดถึงการปิดมีตัวแปรทั้งหมดพร้อมกับฟังก์ชั่นอินสแตนซ์ดังนั้นหากคุณมีการปิด 100 ไบต์และ 1,000 อินสแตนซ์ที่มีหน่วยความจำเพิ่มขึ้น 100kb
Akash Kava

@AkashKava และ @Semra ฉันไม่คิดว่าการทดสอบนี้จะอธิบายได้ว่าทำไมคุณถึงผ่านเข้ามาในสถานการณ์จริง ประสิทธิภาพที่เพิ่มขึ้นนั้นจะเห็นได้ก็ต่อเมื่อคุณมีการปิดที่ซ้อนกันอย่างลึกล้ำที่เข้าถึงตัวแปรนั้นตามขอบเขตของห่วงโซ่ เห็นได้ชัดว่าถ้าฟังก์ชั่นของคุณอยู่ห่างเพียงหนึ่งก้าวจากห่วงโซ่ขอบเขตจากตัวแปรคุณจะไม่ได้รับประโยชน์มากนัก แต่ถ้ามันอยู่ตรงนั้นและจำเป็นที่จะต้องทำให้windowคุณได้เห็นสำเนาของท้องถิ่น
gabereal

@gabereal เอนจิ้นใหม่ของ JavaScript ช่วยแก้ไขการปิดเมื่อมีการคอมไพล์เองไม่มีการเพิ่มประสิทธิภาพเมื่อปิดรันไทม์ ฉันสงสัยว่าจะมีการเพิ่มประสิทธิภาพที่วัดได้หากคุณมั่นใจคุณสามารถสร้างตัวอย่าง jsperf เพื่อแสดงประสิทธิภาพ ประสิทธิภาพเดียวที่เราได้รับคือการแยกการปิดซึ่งทำให้การลดขนาดเล็กลงซึ่งช่วยลดขนาดและประสิทธิภาพทำได้โดยการดาวน์โหลดและแยกสคริปต์ได้เร็วขึ้น
Akash Kava

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

มีการค้นหารหัสที่หน้าต่าง curated ในตอนท้าย แต่ไม่ได้ป้อนในพารามิเตอร์ของฟังก์ชันหมายถึงอะไร ตรวจสอบให้แน่ใจว่าพารามิเตอร์เป็นไปตามวัตถุขอบเขตของโกลบอลดั้งเดิมแม้ว่าจะไม่มีหน้าต่างใดที่ฉันคิดเอาไว้?
Webwoman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.