ความแตกต่างระหว่าง“ module.exports” และ“ exports” ในระบบโมดูล CommonJs


277

ในหน้านี้ ( http://docs.nodejitsu.com/articles/getting-started/what-is-require ) มันระบุว่า "ถ้าคุณต้องการตั้งค่าเอ็กซ์ปอร์ตออบเจ็กต์เป็นฟังก์ชันหรือวัตถุใหม่คุณต้อง ใช้วัตถุ module.exports "

คำถามของฉันคือทำไม

// right
module.exports = function () {
  console.log("hello world")
}
// wrong
exports = function () {
  console.log("hello world")
}

ฉัน console.logged ผล ( result=require(example.js)) และคนแรกคือคนที่สองคือ[Function]{}

คุณช่วยอธิบายเหตุผลเบื้องหลังได้ไหม ผมอ่านโพสต์ที่นี่: module.exports เทียบกับการส่งออกใน Node.js มันมีประโยชน์ แต่ไม่ได้อธิบายถึงสาเหตุที่มันถูกออกแบบในลักษณะนั้น จะมีปัญหาหรือไม่หากมีการส่งคืนการอ้างอิงการส่งออกโดยตรง?


11
module.exportsการใช้งานเสมอ
Gabriel Llamas

1
ฉันคิดว่าคำแนะนำดังกล่าวข้างต้นช่วยให้หลีกเลี่ยงปัญหานี้
Vitalii Korsakov

@GabrielLlamas ดังนั้นทำไมแพคเกจจำนวนมากใช้เพียงexportsตัวอย่างเช่นgithub.com/tj/consolidate.js/blob/master/lib/consolidate.js ?
CodyBugstein

3
@Imray หากคุณมักจะใช้module.exportsคุณจะไม่ผิด แต่คุณสามารถใช้exportsถ้าคุณไม่ได้เปลี่ยนวัตถุเริ่มต้นส่งออก, ที่อยู่, var foo = require('foo').fooถ้าคุณเพียงแค่อายัดทรัพย์สินเช่นนี้ นี้fooคุณสมบัติที่สามารถส่งออกเช่นนี้และแน่นอนยังมีexports.foo = ... module.exportsเป็นตัวเลือกส่วนบุคคล แต่ฉันกำลังใช้งานอยู่module.exportsและexportsเหมาะสม
Gabriel Llamas

ฉันชอบ export.myFunc = function () {} ดังนั้นฉันไม่จำเป็นต้องเก็บรายการส่งออกไว้ที่ด้านล่างของไฟล์ รู้สึกใกล้ชิดกับวิธีปฏิบัติทั่วไปของการส่งออกเมื่อคุณประกาศใน ES6
SacWebDeveloper

คำตอบ:


625

moduleเป็นวัตถุ JavaScript ธรรมดาที่มีexportsคุณสมบัติ exportsเป็นตัวแปร JavaScript module.exportsธรรมดาที่เกิดขึ้นจะถูกตั้งค่า ในตอนท้ายของไฟล์ของคุณ node.js นั้นโดยทั่วไปจะ 'กลับmodule.exportsไปที่requireฟังก์ชั่น วิธีง่าย ๆ ในการดูไฟล์ JS ใน Node อาจเป็น:

var module = { exports: {} };
var exports = module.exports;

// your code

return module.exports;

หากคุณตั้งค่าคุณสมบัติเป็นexportsเช่นexports.a = 9;นั้นจะตั้งค่าmodule.exports.aเช่นกันเนื่องจากวัตถุถูกส่งผ่านไปเป็นข้อมูลอ้างอิงใน JavaScript ซึ่งหมายความว่าหากคุณตั้งค่าตัวแปรหลายตัวเป็นวัตถุเดียวกันพวกมันจะเป็นวัตถุเดียวกันทั้งหมด ดังนั้นexportsและmodule.exportsเป็นวัตถุเดียวกัน
แต่ถ้าคุณตั้งค่าexportsเป็นสิ่งใหม่มันจะไม่ถูกตั้งค่าอีกต่อไปmodule.exportsดังนั้นexportsและmodule.exportsจะไม่เป็นวัตถุเดียวกันอีกต่อไป


11
ใช่มันเป็นแค่พื้นฐานของประเภทการอ้างอิง
Vitalii Korsakov

18
ทำไม!? เหตุใดจึงสามารถอ่านได้เฉพาะที่นี่ สิ่งนี้ควรเป็นสโลแกนสำหรับทุกโมดูลาร์ javaScript ขอบคุณ
lima_fil

8
อธิบายอย่างสวยงาม!
Aakash Verma

3
คำตอบที่ดีที่สุด !!
จอห์น

5
คำอธิบายที่ดี เอกสารประกอบสำหรับmodule.exportsอธิบายเช่นกัน: nodejs.org/api/modules.html#modules_module_exports
Brian Morearty

52

คำตอบของ Renee ได้รับการอธิบายอย่างดี นอกเหนือจากคำตอบด้วยตัวอย่าง:

โหนดทำสิ่งต่างๆมากมายกับไฟล์ของคุณและสิ่งหนึ่งที่สำคัญคือการบีบไฟล์ของคุณ ภายในโหนดซอร์สโค้ด "module.exports" ถูกส่งคืน ลองย้อนกลับไปและทำความเข้าใจกับเสื้อคลุม สมมติว่าคุณมี

greet.js

var greet = function () {
   console.log('Hello World');
};

module.exports = greet;

โค้ดข้างต้นถูกห่อเป็น IIFE (เรียกใช้ฟังก์ชั่นการแสดงออกทันที) ภายในซอร์สโค้ด nodejs ดังนี้:

(function (exports, require, module, __filename, __dirname) { //add by node

      var greet = function () {
         console.log('Hello World');
      };

      module.exports = greet;

}).apply();                                                  //add by node

return module.exports;                                      //add by node

และฟังก์ชั่นดังกล่าวข้างต้นถูกเรียก (.apply ()) และกลับมา module.exports ในเวลานี้โมดูลการส่งออกและส่งออกชี้ไปที่การอ้างอิงเดียวกัน

ทีนี้ลองจินตนาการว่าคุณเขียน greet.js อีกครั้งเป็น

exports = function () {
   console.log('Hello World');
};
console.log(exports);
console.log(module.exports);

ผลลัพธ์จะเป็น

[Function]
{}

เหตุผลคือ: module.exports เป็นวัตถุว่างเปล่า เราไม่ได้ตั้งค่าอะไรให้กับ module.exports แต่เราได้ตั้งค่า export = function () ..... ใน greet.js ใหม่ ดังนั้น module.exports จึงว่างเปล่า

เทคนิคการส่งออกและ module.exports ควรชี้ไปที่การอ้างอิงเดียวกัน (นั่นถูกต้อง !!) แต่เราใช้ "=" เมื่อกำหนด function () .... เพื่อส่งออกซึ่งจะสร้างวัตถุอื่นในหน่วยความจำ ดังนั้นโมดูลการส่งออกและส่งออกจึงให้ผลลัพธ์ที่แตกต่างกัน เมื่อพูดถึงการส่งออกเราไม่สามารถแทนที่มันได้

ทีนี้ลองนึกว่าคุณเขียนใหม่ (นี่เรียกว่าการกลายพันธุ์) greet.js (หมายถึงคำตอบของ Renee) เป็น

exports.a = function() {
    console.log("Hello");
}

console.log(exports);
console.log(module.exports);

ผลลัพธ์จะเป็น

{ a: [Function] }
{ a: [Function] }

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

ข้อสรุปมักจะใช้ module.exports เพื่อหลีกเลี่ยงความสับสน หวังว่านี่จะช่วยได้ Happy coding :)


นี่ก็เป็นคำตอบที่ชาญฉลาดและเป็นคำตอบของ @ goto-bus-stop :)
varun

23

นอกจากนี้สิ่งหนึ่งที่อาจช่วยให้เข้าใจ:

math.js

this.add = function (a, b) {
    return a + b;
};

client.js

var math = require('./math');
console.log(math.add(2,2); // 4;

ยอดเยี่ยมในกรณีนี้:

console.log(this === module.exports); // true
console.log(this === exports); // true
console.log(module.exports === exports); // true

ดังนั้นตามค่าเริ่มต้น "นี่" จึงเท่ากับ module.exports

อย่างไรก็ตามหากคุณเปลี่ยนการใช้งานเป็น:

math.js

var add = function (a, b) {
    return a + b;
};

module.exports = {
    add: add
};

ในกรณีนี้มันจะทำงานได้ดีอย่างไรก็ตาม "นี่" ไม่เท่ากับโมดูลส่งออกอีกต่อไปเพราะวัตถุใหม่ถูกสร้างขึ้น

console.log(this === module.exports); // false
console.log(this === exports); // true
console.log(module.exports === exports); // false

และตอนนี้สิ่งที่จะถูกส่งคืนโดยต้องการคือสิ่งที่ถูกกำหนดไว้ในโมดูลการส่งออกไม่ใช่สิ่งนี้หรือการส่งออกอีกต่อไป

อีกวิธีที่จะทำคือ:

math.js

module.exports.add = function (a, b) {
    return a + b;
};

หรือ:

math.js

exports.add = function (a, b) {
    return a + b;
};

15

คำตอบของ Rene เกี่ยวกับความสัมพันธ์ระหว่างexportsและmodule.exportsค่อนข้างชัดเจนทั้งหมดเกี่ยวกับการอ้างอิง JavaScript เพียงแค่ต้องการเพิ่ม:

เราเห็นสิ่งนี้ในโมดูลโหนดจำนวนมาก:

var app = exports = module.exports = {};

สิ่งนี้จะทำให้แน่ใจว่าแม้ว่าเราเปลี่ยน module.export เรายังคงสามารถใช้การส่งออกได้โดยทำให้ตัวแปรสองตัวนั้นชี้ไปที่วัตถุเดียวกัน


ฉันสับสนกับคำอธิบายนี้
GuyFreakz

6
@GuyFreakz ฉันไม่แน่ใจว่าสิ่งนี้พูดถึงความสับสนของคุณ แต่module.exportsและexportsเป็นเพียงตัวแปรที่แยกต่างหากเริ่มต้นเพื่ออ้างอิงวัตถุเดียวกัน หากคุณเปลี่ยนการอ้างอิงตัวแปรตัวใดตัวหนึ่งตัวแปรทั้งสองนั้นจะไม่อ้างอิงสิ่งเดียวกันอีกต่อไป บรรทัดของโค้ดด้านบนช่วยให้มั่นใจว่าตัวแปรทั้งสองจะเริ่มต้นเป็นวัตถุใหม่เดียวกัน
Andrew Palmer

กรณีการใช้งานจริงที่คนอื่นไม่ได้รับใน @fengshuo ขอบคุณ!
Aakash Verma

0

myTest.js

module.exports.get = function () {};

exports.put = function () {};

console.log(module.exports)
// output: { get: [Function], put: [Function] }

exportsและmodule.exportsเหมือนกันและมีการอ้างอิงไปยังวัตถุเดียวกัน คุณสามารถเพิ่มคุณสมบัติได้ทั้งสองวิธีตามความสะดวกของคุณ

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