รูปแบบ JavaScript นี้เรียกว่าอะไรและเหตุใดจึงใช้


100

ฉันกำลังศึกษาTHREE.jsและสังเกตเห็นรูปแบบที่มีการกำหนดฟังก์ชันดังนี้:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(ตัวอย่างดูวิธีการ raycast ที่นี่ )

ปกติรูปแบบของวิธีการดังกล่าวจะมีลักษณะเช่นนี้

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

การเปรียบเทียบเวอร์ชันแรกกับรูปแบบปกติเวอร์ชันแรกดูเหมือนจะแตกต่างกันในสิ่งนั้น:

  1. จะกำหนดผลลัพธ์ของฟังก์ชันที่ดำเนินการเอง
  2. กำหนดตัวแปรท้องถิ่นภายในฟังก์ชันนี้
  3. ส่งคืนฟังก์ชันจริงที่มีตรรกะที่ใช้ตัวแปรโลคัล

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

ฉันเดาได้ดีที่สุดว่าเหตุใดจึงใช้สิ่งนี้คือ จำกัด จำนวนอินสแตนซ์สำหรับ bar (จะมีเพียงอันเดียว) จึงช่วยประหยัดค่าใช้จ่ายในการจัดการหน่วยความจำ

คำถามของฉัน:

  1. สมมติฐานนี้ถูกต้องหรือไม่?
  2. มีชื่อลายนี้ไหม
  3. ทำไมถึงใช้?

1
@ChrisHayes ยุติธรรมพอ. ฉันติดแท็กเป็น THREE.js เพราะฉันคิดว่าผู้ร่วมให้ข้อมูล THREE.js มีคุณสมบัติเหมาะสมที่สุดที่จะตอบคำถามนี้ แต่ใช่มันเป็นคำถามทั่วไปของ JS
Patrick Klug

2
ฉันเชื่อว่ามันเรียกว่าการปิด คุณสามารถอ่านเกี่ยวกับพวกเขา
StackFlowed

1
หากนี่เป็นเพียงสถานที่เดียวที่บาร์ถูกสร้างอินสแตนซ์แสดงว่าเป็นรูปแบบซิงเกิลตัน
พอล

8
ไม่จำเป็นต้องบันทึกหน่วยความจำ แต่สามารถรักษาสถานะได้ตลอดการเรียก
Juan Mendes

2
@wrongAnswer: ไม่แน่นอน ที่นี่ฟังก์ชันที่ไม่ระบุตัวตน (ซึ่งจะเป็นการปิด) จะถูกเรียกใช้งานทันที
njzk2

คำตอบ:


100

สมมติฐานของคุณเกือบถูกต้อง ลองทบทวนสิ่งเหล่านี้ก่อน

  1. มันกำหนดการกลับมาของฟังก์ชันที่ดำเนินการเอง

สิ่งนี้เรียกว่านิพจน์ฟังก์ชันที่เรียกใช้ทันทีหรือIIFE

  1. กำหนดตัวแปรท้องถิ่นภายในฟังก์ชันนี้

นี่เป็นวิธีการมีฟิลด์ออบเจ็กต์ส่วนตัวใน JavaScript เนื่องจากไม่ได้ให้privateคำสำคัญหรือฟังก์ชันเป็นอย่างอื่น

  1. ส่งคืนฟังก์ชันจริงที่มีตรรกะที่ใช้ตัวแปรโลคัล

อีกครั้งที่จุดหลักคือตัวแปรท้องถิ่นนี้เป็นส่วนตัว

มีชื่อลายนี้ไหม

AFAIK คุณสามารถเรียกรูปแบบนี้แบบโมดูล การอ้างอิง:

รูปแบบโมดูลสรุป "ความเป็นส่วนตัว" สถานะและองค์กรโดยใช้การปิด เป็นวิธีการผสมผสานระหว่างวิธีการและตัวแปรของภาครัฐและเอกชนเพื่อป้องกันชิ้นส่วนต่างๆไม่ให้รั่วไหลไปสู่ขอบเขตทั่วโลกและบังเอิญไปชนกับอินเทอร์เฟซของผู้พัฒนารายอื่น ด้วยรูปแบบนี้จะมีการส่งคืนเฉพาะ API สาธารณะทำให้ทุกอย่างเป็นแบบส่วนตัว

การเปรียบเทียบสองตัวอย่างนี้ฉันเดาได้ดีที่สุดว่าทำไมถึงใช้อันแรกคือ

  1. กำลังใช้รูปแบบการออกแบบ Singleton
  2. เราสามารถควบคุมวิธีสร้างออบเจ็กต์เฉพาะประเภทได้โดยใช้ตัวอย่างแรก การจับคู่ระยะใกล้กับจุดนี้อาจเป็นวิธีการโรงงานแบบคงที่ตามที่อธิบายไว้ใน Effective Java
  3. จะมีประสิทธิภาพหากคุณต้องการสถานะวัตถุเดียวกันทุกครั้ง

แต่ถ้าคุณต้องการวัตถุวานิลลาทุกครั้งรูปแบบนี้อาจไม่เพิ่มมูลค่าใด ๆ


1
+1 สำหรับระบุรูปแบบจากหนังสือของ Addy Osmani อย่างถูกต้อง คุณถูกต้องในการตั้งชื่อของคุณ - นี่คือรูปแบบโมดูล - รูปแบบโมดูลที่เปิดเผยโดยวิธีนี้
Benjamin Gruenbaum

4
ฉันเห็นด้วยกับคำตอบของคุณยกเว้นส่วน "ไม่มีตัวแปรส่วนตัวนอกกรอบ" ตัวแปร JS ทั้งหมดมีการกำหนดขอบเขตคำศัพท์เป็น 'out-of-the-box' ซึ่งเป็นกลไกที่มีประสิทธิภาพ / ทั่วไปมากกว่าตัวแปร "ส่วนตัว" (เช่นที่พบใน Java) ดังนั้น JS จึงสนับสนุนตัวแปร "ส่วนตัว" เป็นกรณีพิเศษในการจัดการกับตัวแปรทั้งหมด
Warbo

ฉันคิดว่าโดย "ตัวแปรส่วนตัว" คุณหมายถึง "ช่องวัตถุ" ส่วนตัว
Ian Ringrose

1
สำหรับรายละเอียด: klauskomenda.com/code/javascript-programming-patterns/#module
Nagaraj Tantri

4
@LukaHorvat: ตามความเป็นจริงจาวาสคริปต์ไม่ได้ "ทรงพลัง" กว่าภาษาอื่น ๆ (ฉันชอบคำที่แสดงออก) อันที่จริงมันแสดงออกน้อยกว่าเนื่องจากวิธีเดียวในการปกป้องตัวแปรของคุณคือการใส่ไว้ในฟังก์ชันเพื่อหลีกเลี่ยงเรื่องไร้สาระที่ใช้ตัวแปรซ้ำ รูปแบบโมดูลเป็นข้อกำหนดขั้นสุดท้ายในการสร้างโค้ดจาวาสคริปต์ที่ดี แต่ไม่ใช่คุณลักษณะของภาษา แต่เป็นวิธีแก้ปัญหาที่น่าเศร้ากว่าเพื่อหลีกเลี่ยงการถูกกัดโดยจุดอ่อนของภาษา
Falanwe

11

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

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

รูปแบบนี้เป็นรูปแบบเฉพาะของการปิด


1
ใน JS มักแสดงผลเป็น 'โมดูล'
Lesha Ogonkov

2
ฉันจะไม่เรียกมันว่า "รูปแบบเฉพาะของการปิด" ต่อเซ เป็นรูปแบบที่ใช้การปิด ชื่อของรูปแบบยังคงเป็นที่ยอมรับ
Chris Hayes

4
ต้องการชื่อจริงหรือ? ทุกอย่างต้องเป็นแบบแผนหรือไม่? เราต้องการอนุกรมวิธานที่ไม่มีที่สิ้นสุดของตัวแปร "รูปแบบโมดูล" หรือไม่? เป็นเพียง "IIFE ที่มีตัวแปรท้องถิ่นที่ส่งคืนฟังก์ชัน" ไม่ได้หรือ
Dagg Nabbit

3
@DaggNabbit เมื่อคำถามคือ "รูปแบบนี้เรียกว่าอะไร"? ใช่มันต้องมีชื่อหรือเหตุผลอื่นที่น่าเชื่อว่ามันไม่มี นอกจากนี้ยังมีรูปแบบด้วยเหตุผล ฉันไม่เข้าใจว่าทำไมคุณถึงดูถูกพวกเขาที่นี่
Chris Hayes

4
@ChrisHayes ถ้ามันต้องการชื่อทำไมคุณถึงต้องโต้แย้งว่ามันไม่มี? นั่นไม่สมเหตุสมผลเลย หากต้องการหนึ่งก็ต้องไม่มี ฉันไม่มีปัญหากับรูปแบบ แต่ฉันไม่คิดว่ามันจำเป็นที่จะต้องจำแนกสำนวนง่ายๆทุกสำนวนให้เป็นแบบแผน การทำเช่นนั้นนำไปสู่การคิดในรูปแบบที่ จำกัด ("นี่คือรูปแบบโมดูลหรือไม่ฉันใช้รูปแบบโมดูลถูกต้องหรือไม่" เทียบกับ "ฉันมี IIFE ที่มีตัวแปรท้องถิ่นบางตัวที่ส่งคืนฟังก์ชันการออกแบบนี้ใช้ได้กับฉันหรือไม่")
Dagg Nabbit

8

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

การปิด (ระบุโดยฟังก์ชันภายในฟังก์ชัน) ทำให้มั่นใจได้ว่าฟังก์ชันภายในสามารถเข้าถึงตัวแปรภายในฟังก์ชันด้านนอกได้

ในตัวอย่างที่คุณให้ฟังก์ชั่นด้านในจะถูกส่งกลับ (และได้รับมอบหมายให้foo) โดยการดำเนินการฟังก์ชั่นด้านนอกซึ่งหมายความว่าtmpObjectยังคงอาศัยอยู่ภายในปิดและสายหลายฟังก์ชั่นภายในจะดำเนินการในกรณีเดียวกันของfoo()tmpObject


5

ความแตกต่างที่สำคัญระหว่างรหัสของคุณและรหัส Three.js คือในโค้ด Three.js ตัวแปรtmpObjectจะเริ่มต้นเพียงครั้งเดียวจากนั้นแชร์โดยทุกการเรียกใช้ฟังก์ชันที่ส่งคืน

สิ่งนี้จะมีประโยชน์ในการรักษาสถานะบางอย่างระหว่างการเรียกเช่นเดียวกับการใช้staticตัวแปรในภาษาคล้าย C

tmpObject เป็นตัวแปรส่วนตัวที่มองเห็นได้เฉพาะกับฟังก์ชันภายในเท่านั้น

มันเปลี่ยนการใช้หน่วยความจำ แต่ไม่ได้ออกแบบมาเพื่อบันทึกหน่วยความจำ


5

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

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

ในกรณีหลังนี้วิธีการบวกจะถูกเรียกว่า Calculator.add ();


0

ในตัวอย่างที่ให้มาตัวอย่างแรกจะใช้อินสแตนซ์เดียวกันของ tmpObject สำหรับทุกการเรียกใช้ฟังก์ชัน foo () โดยที่ในตัวอย่างข้อมูลที่สอง tmpObject จะเป็นอินสแตนซ์ใหม่ทุกครั้ง

เหตุผลหนึ่งที่อาจมีการใช้ snippet แรกคือตัวแปร tmpObject สามารถใช้ร่วมกันระหว่างการเรียกไปยัง foo () โดยที่ค่าของมันไม่รั่วไหลออกไปในขอบเขตที่ประกาศ foo ()

เวอร์ชันฟังก์ชันที่ไม่ได้ดำเนินการทันทีของข้อมูลโค้ดแรกจะมีลักษณะดังนี้:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

อย่างไรก็ตามโปรดทราบว่าเวอร์ชันนี้มี tmpObject ในขอบเขตเดียวกันกับ foo () ดังนั้นจึงสามารถจัดการได้ในภายหลัง

วิธีที่ดีกว่าในการใช้งานฟังก์ชันเดียวกันคือการใช้โมดูลแยกต่างหาก:

โมดูล 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

โมดูล 2:

var foo = require('./foo');

การเปรียบเทียบระหว่างประสิทธิภาพของ IEF และฟังก์ชันผู้สร้าง foo ที่มีชื่อ: http://jsperf.com/ief-vs-named-function


3
ตัวอย่าง 'ดีกว่า' ของคุณใช้ได้เฉพาะใน NodeJS และคุณยังไม่ได้อธิบายว่ามันดีกว่าอย่างไร
Benjamin Gruenbaum

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

โมดูล @BenjaminGruenbaum ไม่เพียง แต่ในโหนดเท่านั้นยังมีโซลูชันโมดูลฝั่งไคลเอ็นต์อีกมากมายเช่น browserify ฉันคิดว่าโซลูชันโมดูลนั้น "ดีกว่า" เนื่องจากอ่านได้ง่ายขึ้นแก้ไขจุดบกพร่องได้ง่ายขึ้นและมีความชัดเจนมากขึ้นเกี่ยวกับสิ่งที่อยู่ในขอบเขตและที่ใด
Kory Nunn
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.