อะไรคือวัตถุประสงค์ของ Node.js module.exports และคุณใช้มันอย่างไร?


1432

อะไรคือวัตถุประสงค์ของ Node.js module.exports และคุณใช้มันอย่างไร?

ฉันดูเหมือนจะไม่พบข้อมูลใด ๆ เกี่ยวกับสิ่งนี้ แต่มันดูเหมือนจะเป็นส่วนสำคัญของ Node.js เพราะฉันมักจะเห็นมันในซอร์สโค้ด

ตามเอกสาร Node.js :

โมดูล

moduleอ้างอิงถึงปัจจุบัน โดยเฉพาะอย่างยิ่งmodule.exports เหมือนกับวัตถุส่งออก ดู src/node.jsข้อมูลเพิ่มเติม

แต่มันก็ไม่ได้ช่วยอะไร

ทำอะไรกันแน่module.exportsและตัวอย่างง่ายๆจะเป็นอย่างไร

คำตอบ:


1590

module.exportsเป็นวัตถุที่ส่งคืนจริง ๆ แล้วเป็นผลมาจากการrequireโทร

exportsตัวแปรเป็นชุดแรกไปยังวัตถุเดียวกันนั้น (คือมันเป็นชวเลข "สมญานาม") ดังนั้นในรหัสโมดูลที่คุณจะมักจะเขียนอะไรเช่นนี้

let myFunc1 = function() { ... };
let myFunc2 = function() { ... };
exports.myFunc1 = myFunc1;
exports.myFunc2 = myFunc2;

เพื่อการส่งออก (หรือ "เผย") ฟังก์ชั่นที่กำหนดขอบเขตภายในและmyFunc1myFunc2

และในรหัสโทรที่คุณจะใช้:

const m = require('./mymodule');
m.myFunc1();

โดยที่บรรทัดสุดท้ายแสดงให้เห็นว่าผลลัพธ์ของการrequire(โดยปกติ) เป็นเพียงวัตถุธรรมดาที่คุณสมบัติอาจเข้าถึงได้

หมายเหตุ: ถ้าคุณเขียนทับแล้วมันจะไม่ได้หมายถึงexports module.exportsดังนั้นหากคุณต้องการกำหนดวัตถุใหม่ (หรืออ้างอิงฟังก์ชัน) ให้กับexportsคุณคุณควรกำหนดวัตถุใหม่นั้นให้module.exports


เป็นที่น่าสังเกตว่าชื่อที่เพิ่มลงในexportsวัตถุนั้นไม่จำเป็นต้องเหมือนกับชื่อที่กำหนดขอบเขตภายในของโมดูลสำหรับค่าที่คุณกำลังเพิ่มดังนั้นคุณอาจมี:

let myVeryLongInternalName = function() { ... };
exports.shortName = myVeryLongInternalName;
// add other objects, functions, as required

ติดตามโดย:

const m = require('./mymodule');
m.shortName(); // invokes module.myVeryLongInternalName

119
คำตอบที่ดี - สำหรับฉันดูเหมือนว่า 'exposes' จะเป็นทางเลือกที่ดีกว่าคำศัพท์ 'ส่งออก'
UpTheCreek

2
@ApopheniaOverload - คุณสามารถทำ "exports.func1, exports.func2, etc" เพื่อให้มีวิธีการเปิดเผยหลายวิธีจากไฟล์เดียว
hellatan

73
โมดูลที่ต้องการควรเป็นvar m = require ('./ mymodule'); ด้วยจุดและเครื่องหมายทับ วิธีนี้ Node.js รู้ว่าเรากำลังใช้โมดูลโลคัล
Gui Premonsa

7
ต้องแน่ใจว่าใช้: require ('./ module_name') ไวยากรณ์เนื่องจากอาจมีโมดูล node.js อื่น ๆ ที่มีชื่อบางชื่อและแทนที่จะเลือกโมดูลของคุณเองมันจะเลือกโมดูลที่ติดตั้งด้วย node.js
Sazid

3
@UpTheCreek มีประเพณีที่ยาวนานในการอ้างถึงสัญลักษณ์สาธารณะที่แสดงโดยโมดูลว่าเป็น 'ส่งออก' ซึ่งย้อนกลับไปในระบบการเขียนโปรแกรมจำนวนมากและทศวรรษที่ผ่านมา นี่ไม่ใช่คำใหม่ที่คิดค้นโดยนักพัฒนาโหนด
Mark Reed

218

สิ่งนี้ได้รับคำตอบแล้ว แต่ฉันต้องการเพิ่มการชี้แจงบางอย่าง ...

คุณสามารถใช้ทั้งexportsและmodule.exportsเพื่อนำเข้ารหัสในแอปพลิเคชันของคุณดังนี้:

var mycode = require('./path/to/mycode');

กรณีการใช้งานพื้นฐานที่คุณจะเห็น (เช่นในรหัสตัวอย่าง ExpressJS) คือคุณตั้งค่าคุณสมบัติบนexportsวัตถุในไฟล์. js ที่คุณนำเข้าโดยใช้require()

ดังนั้นในตัวอย่างการนับอย่างง่ายคุณอาจมี:

(counter.js):

var count = 1;

exports.increment = function() {
    count++;
};

exports.getCount = function() {
    return count;
};

... จากนั้นในแอปพลิเคชันของคุณ (web.js หรือไฟล์. js อื่น ๆ ):

var counting = require('./counter.js');

console.log(counting.getCount()); // 1
counting.increment();
console.log(counting.getCount()); // 2

ในแง่ง่ายที่คุณสามารถคิดของไฟล์ต้องเป็นฟังก์ชั่นที่ส่งกลับวัตถุเดียวและคุณสามารถเพิ่มคุณสมบัติ (สตริง, ตัวเลข, อาร์เรย์ฟังก์ชั่นอะไร) exportsไปยังวัตถุที่ส่งกลับโดยการตั้งค่าพวกเขาใน

บางครั้งคุณอาจต้องการให้วัตถุที่ส่งคืนจากการrequire()เรียกเป็นฟังก์ชั่นที่คุณสามารถโทรได้มากกว่าแค่วัตถุที่มีคุณสมบัติ ในกรณีนี้คุณต้องตั้งค่าmodule.exportsเช่นนี้:

(sayhello.js):

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

(app.js):

var sayHello = require('./sayhello.js');
sayHello(); // "Hello World!"

ความแตกต่างระหว่างการส่งออกและ module.exports จะมีการอธิบายที่ดีกว่าในคำตอบนี้ที่นี่


ฉันจะเรียกต้องใช้โมดูลจากโฟลเดอร์อื่นที่ไม่มีโฟลเดอร์รูทเป็นของฉันได้อย่างไร
Igal

@ user301639 คุณสามารถใช้พา ธ สัมพัทธ์เพื่อสำรวจลำดับชั้นของระบบไฟล์ requireเริ่มสัมพันธ์กับโฟลเดอร์ที่คุณเรียกใช้node app.jsฉันขอแนะนำให้คุณโพสต์คำถามใหม่พร้อมรหัสอย่างชัดเจน + ตัวอย่างโครงสร้างโฟลเดอร์เพื่อรับคำตอบที่ชัดเจนยิ่งขึ้น
Jed Watson

1
ฉันต้องปรับแต่ง module.export ตัวอย่างของคุณเพื่อให้มันใช้งานได้ ไฟล์: var sayHello = require('./ex6_module.js'); console.log(sayHello());และโมดูล:module.exports = exports = function() { return "Hello World!"; }
เจสัน Lydon

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

module.exports = exports = function(){...}ที่สองexportsเป็นเพียงตัวแปรใช่มั้ย อาจเป็นได้module.exports = abc = function()
Jeb50

60

โปรดทราบว่ากลไกโมดูล NodeJS นั้นใช้โมดูลCommonJSซึ่งรองรับการใช้งานอื่น ๆ เช่นRequireJSแต่ยังSproutCore , CouchDB , Wakanda , OrientDB , ArangoDB , RingoJS , TeaJS , SilkJS , curl.jsหรือแม้แต่Adobe Photoshop (ผ่านPSLib ) คุณสามารถค้นหารายการเต็มรูปแบบของการใช้งานที่รู้จักกันที่นี่

ถ้าโมดูลของคุณใช้คุณสมบัติหรือโมดูลเฉพาะของโหนดฉันขอแนะนำให้คุณใช้exportsแทนmodule.exports ซึ่งไม่ได้เป็นส่วนหนึ่งของมาตรฐาน CommonJSและส่วนใหญ่จะไม่ได้รับการสนับสนุนจากการใช้งานอื่น ๆ

คุณลักษณะเฉพาะของ NodeJS อีกอย่างหนึ่งคือเมื่อคุณกำหนดการอ้างอิงไปยังวัตถุใหม่exportsแทนที่จะเพิ่มคุณสมบัติและวิธีการเช่นเดียวกับในตัวอย่างล่าสุดที่จัดทำโดย Jed Watson ในชุดข้อความนี้ ฉันจะไม่สนับสนุนการปฏิบัตินี้เป็นการส่วนตัวเนื่องจากจะเป็นการแบ่งการสนับสนุนการอ้างอิงแบบวงกลมของกลไกโมดูล CommonJS จากนั้นจะไม่ได้รับการสนับสนุนจากการใช้งานทั้งหมดและตัวอย่างของ Jed ควรถูกเขียนด้วยวิธีนี้ (หรือหนึ่งที่คล้ายกัน) เพื่อจัดเตรียมโมดูลที่เป็นสากลมากขึ้น:

(sayhello.js):

exports.run = function() {
    console.log("Hello World!");
}

(app.js):

var sayHello = require('./sayhello');
sayHello.run(); // "Hello World!"

หรือใช้คุณสมบัติ ES6

(sayhello.js):

Object.assign(exports, {
    // Put all your public API here
    sayhello() {
        console.log("Hello World!");
    }
});

(app.js):

const { sayHello } = require('./sayhello');
sayHello(); // "Hello World!"

PS: ดูเหมือนว่า Appcelerator ยังใช้โมดูล CommonJS แต่ไม่มีการสนับสนุนการอ้างอิงแบบวงกลม (ดู: โมดูล Appcelerator และ CommonJS (แคชและการอ้างอิงแบบวงกลม) )


35

บางสิ่งที่คุณต้องระมัดระวังหากคุณกำหนดการอ้างอิงไปยังวัตถุใหม่ให้กับexportsและ / หรือmodules.exports:

1. คุณสมบัติ / วิธีการทั้งหมดที่แนบมาก่อนหน้านี้กับต้นฉบับexportsหรือmodule.exportsของหลักสูตรที่สูญหายเนื่องจากวัตถุที่ส่งออกจะอ้างอิงใหม่อีกครั้ง

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

exports.method1 = function () {}; // exposed to the original exported object
exports.method2 = function () {}; // exposed to the original exported object

module.exports.method3 = function () {}; // exposed with method1 & method2

var otherAPI = {
    // some properties and/or methods
}

exports = otherAPI; // replace the original API (works also with module.exports)

2. ในกรณีที่หนึ่งexportsหรือmodule.exportsอ้างอิงค่าใหม่พวกเขาจะไม่อ้างอิงวัตถุเดียวกันอีกต่อไป

exports = function AConstructor() {}; // override the original exported object
exports.method2 = function () {}; // exposed to the new exported object

// method added to the original exports object which not exposed any more
module.exports.method3 = function () {}; 

3. ผลที่ยุ่งยาก หากคุณเปลี่ยนการอ้างอิงถึงทั้งสองexportsและmodule.exportsยากที่จะพูดว่า API ใดที่เปิดเผย (ดูเหมือนว่าmodule.exportsชนะ)

// override the original exported object
module.exports = function AConstructor() {};

// try to override the original exported object
// but module.exports will be exposed instead
exports = function AnotherConstructor() {}; 

29

คุณสมบัติ module.exports หรือวัตถุส่งออกอนุญาตให้โมดูลเลือกสิ่งที่ควรใช้ร่วมกับแอปพลิเคชัน

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

ฉันมีวิดีโอบน module_export แล้วที่นี่


18

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

จำเมื่อเขียนโมดูล

  • โหลดโมดูลถูกแคชเฉพาะการโทรเริ่มต้นประเมิน JavaScript
  • เป็นไปได้ที่จะใช้ตัวแปรท้องถิ่นและฟังก์ชั่นภายในโมดูลทุกอย่างไม่จำเป็นต้องส่งออก
  • module.exportsวัตถุยังมีอยู่เป็นexportsชวเลข module.exportsแต่เมื่อกลับมาฟังก์ชั่นเดียวใช้เสมอ

แผนภาพการส่งออกโมดูล

ตามที่: "โมดูลส่วนที่ 2 - การเขียนโมดูล"


9

ลิงก์อ้างอิงเป็นดังนี้:

exports = module.exports = function(){
    //....
}

คุณสมบัติของexportsหรือmodule.exportsเช่นฟังก์ชันหรือตัวแปรจะถูกเปิดเผยภายนอก

มีบางสิ่งที่คุณต้องให้ความสนใจมากขึ้น: อย่าoverrideส่งออก

ทำไม

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

ตัวอย่างที่ดี:

exports.name = 'william';

exports.getName = function(){
   console.log(this.name);
}

ตัวอย่างที่ไม่ดี:

exports = 'william';

exports = function(){
     //...
}

หากคุณแค่ต้องการสัมผัสกับฟังก์ชั่นหรือตัวแปรเพียงตัวเดียวเช่นนี้

// test.js
var name = 'william';

module.exports = function(){
    console.log(name);
}   

// index.js
var test = require('./test');
test();

โมดูลนี้เปิดเผยเพียงหนึ่งฟังก์ชั่นและทรัพย์สินของชื่อเป็นส่วนตัวสำหรับภายนอก


6

มีบางโมดูลดีฟอลต์หรือโมดูลที่มีอยู่ใน node.js เมื่อคุณดาวน์โหลดและติดตั้ง node.js เช่นhttp, sysเป็นต้น

เนื่องจากมีอยู่แล้วใน node.js เมื่อเราต้องการใช้โมดูลเหล่านี้เราจึงมักจะชอบนำเข้าโมดูลแต่ทำไม? เพราะมันมีอยู่แล้วใน node.js การอิมพอร์ตเหมือนกับการนำมาจาก node.js และนำไปไว้ในโปรแกรมของคุณ แล้วใช้มัน

ในขณะที่การส่งออกเป็นสิ่งที่ตรงกันข้ามคุณกำลังสร้างโมดูลที่คุณต้องการสมมติว่าโมดูลเพิ่มเติม.jsและวางโมดูลนั้นใน node.js คุณทำได้โดยการส่งออก

ก่อนที่ฉันจะเขียนอะไรที่นี่โปรดจำไว้ว่าmodule.exports.additionTwoเหมือนกับการส่งออกadditionTwo

นั่นคือเหตุผลที่เราชอบ

exports.additionTwo = function(x)
{return x+2;};

ระวังเส้นทาง

สมมติว่าคุณได้สร้างโมดูลส่วนเสริม

exports.additionTwo = function(x){
return x + 2;
};

เมื่อคุณเรียกใช้งานบนพรอมต์คำสั่ง NODE.JS ของคุณ:

node
var run = require('addition.js');

ข้อผิดพลาดนี้จะบอกว่า

ข้อผิดพลาด: ไม่พบโมดูลนอกจากนี้

นี่เป็นเพราะกระบวนการ node.js ไม่สามารถเพิ่มส่วนต่อไปได้เนื่องจากเราไม่ได้กล่าวถึงเส้นทาง ดังนั้นเราสามารถกำหนดเส้นทางโดยใช้ NODE_PATH

set NODE_PATH = path/to/your/additon.js

ตอนนี้สิ่งนี้จะทำงานได้สำเร็จโดยไม่มีข้อผิดพลาดใด ๆ !!

อีกอย่างหนึ่งคุณยังสามารถเรียกใช้ไฟล์ส่วนเสริมโดยไม่ตั้งค่า NODE_PATH กลับไปที่พรอมต์คำสั่ง nodejs ของคุณ:

node
var run = require('./addition.js');

เนื่องจากเรากำลังจัดหาเส้นทางที่นี่โดยบอกว่ามันอยู่ในไดเรกทอรีปัจจุบัน./สิ่งนี้ควรทำงานได้สำเร็จ


1
มันคือการส่งออกหรือการส่งออก?
rudrasiva86

ขอบคุณสำหรับความช่วยเหลือ :)
JumpMan

3

โมดูล encapsulate รหัสที่เกี่ยวข้องลงในหน่วยของรหัสเดียว เมื่อสร้างโมดูลสิ่งนี้สามารถตีความได้ว่าเป็นการย้ายฟังก์ชั่นที่เกี่ยวข้องทั้งหมดลงในไฟล์

สมมติว่ามีไฟล์ Hello.js ซึ่งรวมถึงสองฟังก์ชั่น

sayHelloInEnglish = function() {
  return "Hello";
};
sayHelloInSpanish = function() {
  return "Hola";
};

เราเขียนฟังก์ชั่นเฉพาะเมื่อยูทิลิตี้ของรหัสมากกว่าหนึ่งสาย

สมมติว่าเราต้องการเพิ่มยูทิลิตี้ของฟังก์ชั่นให้กับไฟล์อื่น ๆ ที่เรียกว่า World.js ในกรณีนี้การส่งออกไฟล์จะปรากฏเป็นรูปภาพซึ่งสามารถรับได้โดย module.exports

คุณสามารถส่งออกทั้งฟังก์ชั่นด้วยรหัสที่ระบุด้านล่าง

var anyVariable={
 sayHelloInEnglish = function() {
      return "Hello";
    };
  sayHelloInSpanish = function() {
      return "Hola";
    }; 
}
module.export=anyVariable;

ตอนนี้คุณเพียงแค่ต้องการชื่อไฟล์ลงในคำสั่ง World.js เพื่อใช้ฟังก์ชั่นเหล่านั้น

var world= require("./hello.js");

ขอบคุณถ้ามันได้ช่วยคุณโปรดยอมรับคำตอบของฉัน :)
Shantanu Madane

1
สายไปงานปาร์ตี้ช้าหน่อย :)
Ben Taliadoros

@ BenTaliadoros ฉันก็คิดว่าเขามาสายและฉันก็คิดว่าวัตถุที่ผันแปรได้ของเขามีข้อผิดพลาดมากมาย บรรทัดด้านบนวิธีการ sayHelloInSpanish ไม่ควรลงท้ายด้วยเครื่องหมายอัฒภาค (;) และ sayHelloInSpanish = ฟังก์ชันผิด ทุกสิ่งผิดปกติกับวัตถุนี้ ฉันจะแก้ไขคำตอบของเขา
พระเจ้า

1
การแก้ไขถูกปิดใช้งาน alphadogg แก้ไขอะไรอีกในคำตอบนี้?
พระเจ้า

เพียงแค่จัดรูปแบบ เว้นแต่มันจะเป็นสิ่งที่บ้าคลั่ง es6 ที่ฉันไม่ได้เจอและฉันก็ไม่แน่ใจเลยมันไม่ใช่ JS เลย
Ben Taliadoros

2

เจตนาคือ:

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

วิกิพีเดีย

ฉันคิดว่ามันยากที่จะเขียนโปรแกรมขนาดใหญ่โดยไม่มีรหัสโมดูลาร์ / ใช้ซ้ำได้ ใน nodejs เราสามารถสร้างโปรแกรมแบบแยกส่วนการใช้การกำหนดสิ่งที่เราสัมผัสและเขียนโปรแกรมของเราด้วยmodule.exportsrequire

ลองตัวอย่างนี้:

fileLog.js

function log(string) { require('fs').appendFileSync('log.txt',string); }

module.exports = log;

stdoutLog.js

function log(string) { console.log(string); }

module.exports = log;

program.js

const log = require('./stdoutLog.js')

log('hello world!');

ปฏิบัติ

$ node program.js

สวัสดีชาวโลก!

ตอนนี้ให้ลองสลับ. /stdoutLog.jsสำหรับ. /fileLog.js


1

วัตถุประสงค์ของระบบโมดูลคืออะไร?

มันบรรลุสิ่งต่อไปนี้:

  1. เก็บไฟล์ของเราไม่ให้มีขนาดใหญ่เกินไป การมีไฟล์ที่มีโค้ดจำนวน 5,000 บรรทัดมักเป็นเรื่องยากที่จะจัดการในระหว่างการพัฒนา
  2. บังคับให้แยกความกังวล การมีรหัสของเราแยกเป็นหลายไฟล์ทำให้เรามีชื่อไฟล์ที่เหมาะสมสำหรับทุกไฟล์ วิธีนี้เราสามารถระบุสิ่งที่ทุกโมดูลทำและหาได้ง่าย (สมมติว่าเราสร้างโครงสร้างไดเรกทอรีแบบลอจิคัลซึ่งยังคงเป็นความรับผิดชอบของคุณ)

การมีโมดูลทำให้ง่ายต่อการค้นหาบางส่วนของรหัสซึ่งทำให้รหัสของเราสามารถบำรุงรักษาได้มากขึ้น

มันทำงานยังไง?

NodejS ใช้ระบบโมดูล CommomJS ซึ่งทำงานในลักษณะดังต่อไปนี้:

  1. หากไฟล์ต้องการส่งออกบางสิ่งบางอย่างมันจะต้องประกาศโดยใช้module.exportไวยากรณ์
  2. หากไฟล์ต้องการนำเข้าบางสิ่งจำเป็นต้องประกาศไฟล์โดยใช้require('file')ไวยากรณ์

ตัวอย่าง:

test1.js

const test2 = require('./test2');    // returns the module.exports object of a file

test2.Func1(); // logs func1
test2.Func2(); // logs func2

test2.js

module.exports.Func1 = () => {console.log('func1')};

exports.Func2 = () => {console.log('func2')};

สิ่งที่มีประโยชน์อื่น ๆ ที่ควรทราบ:

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

-3
let test = function() {
    return "Hello world"
};
exports.test = test;

4
นี่คือตัวอย่างที่คล้ายกันในตัวอย่างข้อมูลแรกในคำตอบที่ยอมรับ ( return "Hello world"ไม่สร้างความแตกต่าง) แต่ไม่มีคำอธิบายใด ๆ โปรดตรวจสอบให้แน่ใจก่อนตอบว่าคำตอบของคุณจะเพิ่มบางสิ่งลงในหัวเรื่อง
barbsan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.