วิธีการ“ ถูกต้อง” สร้างวัตถุที่กำหนดเองใน JavaScript ได้อย่างไร


471

ฉันสงสัยว่าวิธีที่ดีที่สุดคือการสร้างวัตถุ JavaScript ที่มีคุณสมบัติและวิธีการ

ฉันเคยเห็นตัวอย่างที่บุคคลvar self = thisนั้นใช้แล้วใช้self.ในทุกฟังก์ชั่นเพื่อให้แน่ใจว่าขอบเขตนั้นถูกต้องเสมอ

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

มีคนให้ตัวอย่างที่ถูกต้องของวัตถุ JavaScript กับคุณสมบัติและวิธีการบางอย่างได้ไหม


13
ไม่มีวิธี "ดีที่สุด"
Triptych

ไม่ใช่ selfคำที่สงวนไว้ใช่ไหม ถ้าไม่มันควรจะเป็น; เนื่องจากselfเป็นตัวแปรที่กำหนดไว้ล่วงหน้าซึ่งอ้างอิงถึงหน้าต่างปัจจุบัน self === window
Shaz

2
@Shaz: มันไม่ได้เป็นคำสงวนมากกว่าคุณสมบัติอื่น ๆ ของwindowในรูปแบบวัตถุเบราว์เซอร์เช่นdocumentหรือframes; คุณสามารถใช้ตัวระบุอีกครั้งเป็นชื่อตัวแปรได้อย่างแน่นอน แม้ว่าใช่โวหารฉันชอบที่var that= thisจะหลีกเลี่ยงความสับสนที่เป็นไปได้ แม้ว่าwindow.selfท้ายที่สุดแล้วจะไร้จุดหมายดังนั้นจึงไม่มีเหตุผลที่จะสัมผัส
bobince

7
เมื่อ JS ถูกthisย่อขนาดการกำหนดตัวแปรท้องถิ่น (เช่นself) จะลดขนาดไฟล์
แพทริคฟิชเชอ

ลิงค์ใหม่ของ Classjs: github.com/divio/classjs
Nikola

คำตอบ:


889

มีสองรุ่นสำหรับการนำคลาสและอินสแตนซ์มาใช้ใน JavaScript: วิธีการสร้างต้นแบบและวิธีการปิด ทั้งสองมีข้อดีและข้อเสียและมีรูปแบบที่หลากหลายมากมาย โปรแกรมเมอร์และห้องสมุดหลายคนมีวิธีการที่แตกต่างกันและฟังก์ชั่นยูทิลิตี้การจัดการชั้นเรียนเพื่อกระดาษมากกว่าบางส่วนของภาษาที่น่าเกลียด

ผลที่ได้คือใน บริษัท ที่ผสมคุณจะมี mashmash ของ metaclasses ทั้งหมดทำงานแตกต่างกันเล็กน้อย มีอะไรที่แย่กว่านั้นคือเนื้อหาการสอน JavaScript ส่วนใหญ่นั้นแย่มากและมีการประนีประนอมระหว่างการปกปิดฐานทั้งหมดทำให้คุณสับสนมาก (อาจเป็นไปได้ว่าผู้เขียนสับสนเช่นกันโมเดลวัตถุของ JavaScript นั้นแตกต่างอย่างมากกับภาษาการเขียนโปรแกรมส่วนใหญ่และในหลาย ๆ สถานที่ได้รับการออกแบบอย่างไม่ดีนัก)

มาเริ่มด้วยวิธีต้นแบบกันก่อน นี่เป็น native-JavaScript ที่คุณจะได้รับ: มีรหัสค่าใช้จ่ายขั้นต่ำและ instanceof จะทำงานกับอินสแตนซ์ของวัตถุประเภทนี้

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

เราสามารถเพิ่มวิธีการไปยังอินสแตนซ์ที่สร้างขึ้นnew Shapeโดยการเขียนพวกเขาไปยังการprototypeค้นหาของฟังก์ชั่นคอนสตรัคนี้:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

ทีนี้ก็ทำการ subclass มันเท่าที่คุณสามารถเรียกสิ่งที่ JavaScript ทำ subclassing เราทำเช่นนั้นโดยแทนที่prototypeคุณสมบัติมหัศจรรย์แปลก ๆ ทั้งหมด:

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

ก่อนที่จะเพิ่มวิธีการ:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

ตัวอย่างนี้จะใช้งานได้และคุณจะเห็นรหัสเหมือนในบทช่วยสอนจำนวนมาก แต่มนุษย์นั่นnew Shape()น่าเกลียด: เราสร้างคลาสฐานขึ้นทันทีแม้ว่าจะไม่มีการสร้างรูปร่างจริง มันเกิดขึ้นกับการทำงานในกรณีง่าย ๆ นี้เพราะจาวาสคริปต์นั้นเลอะเทอะมาก: มันยอมให้มีการส่งผ่านค่าศูนย์ไม่ได้, ในกรณีนี้xและyกลายเป็นundefinedและถูกกำหนดให้กับต้นแบบthis.xและthis.yและถ้าฟังก์ชั่นคอนสตรัคเตอร์ทำอะไรที่ซับซ้อนกว่านี้มันจะตกลงบนใบหน้า

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

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

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

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

แทนการ new Shape()ผิด ตอนนี้เรามีชุดพื้นฐานที่ยอมรับได้สำหรับคลาสที่สร้างขึ้น

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

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

ทั้งสองเวอร์ชันมีข้อเสียเปรียบที่ไม่สามารถสืบทอดฟังก์ชันคอนสตรัคได้เนื่องจากมีหลายภาษา ดังนั้นแม้ว่าคลาสย่อยของคุณจะไม่เพิ่มอะไรเลยในกระบวนการก่อสร้างคุณต้องจำไว้ว่าให้เรียกตัวสร้างฐานพร้อมอาร์กิวเมนต์ใด ๆ ก็ตามที่ฐานต้องการ สิ่งนี้สามารถทำงานอัตโนมัติได้เล็กน้อยapplyแต่คุณยังคงต้องเขียนออกมา:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

ดังนั้นส่วนขยายทั่วไปคือการแบ่งเนื้อหาการเริ่มต้นเป็นฟังก์ชันของตัวเองมากกว่าตัวสร้างเอง ฟังก์ชั่นนี้สามารถสืบทอดมาจากฐานได้ดี:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

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

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

... ซึ่งเริ่มจะดูเหมือนภาษาอื่น ๆ อีกเล็กน้อยถึงแม้ว่าจะมีไวยากรณ์ที่อุ้ยอ้ายเล็กน้อย คุณสามารถโรยด้วยคุณสมบัติพิเศษบางอย่างได้ถ้าต้องการ บางทีคุณอาจต้องการmakeSubclassจดชื่อชั้นเรียนและตั้งค่าเริ่มต้นให้toStringใช้ บางทีคุณอาจต้องการให้ Constructor ตรวจพบเมื่อมีการเรียกใช้โดยไม่ตั้งใจโดยไม่ต้องnewดำเนินการ (ซึ่งมักจะทำให้การดีบักน่ารำคาญมาก):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

บางทีคุณอาจต้องการที่จะส่งสมาชิกใหม่ทั้งหมดและmakeSubclassเพิ่มพวกเขาไปยังต้นแบบเพื่อช่วยให้คุณไม่ต้องเขียนClass.prototype...มากนัก ระบบคลาสจำนวนมากทำเช่นนั้น:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

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


ทางปิดนั้น นี่เป็นการหลีกเลี่ยงปัญหาของการสืบทอดตามต้นแบบของ JavaScript โดยไม่ใช้การสืบทอดเลย แทน:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

ตอนนี้ทุกอินสแตนซ์เดียวShapeจะมีสำเนาของตัวเองtoStringวิธีการ (และวิธีการอื่น ๆ หรือสมาชิกชั้นเรียนอื่น ๆ ที่เราเพิ่ม)

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

[เนื่องจากไม่มีมรดกที่นี่instanceofผู้ประกอบการจะไม่ทำงาน คุณจะต้องจัดเตรียมกลไกของคุณเองสำหรับการดมกลิ่นในชั้นเรียนหากคุณต้องการ ในขณะที่คุณสามารถคลุกคลีวัตถุต้นแบบในลักษณะเดียวกันกับการสืบทอดต้นแบบมันค่อนข้างยุ่งยากและไม่คุ้มค่ากับinstanceofการทำงาน]

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

var ts= mycircle.toString;
alert(ts());

จากนั้นthisภายในเมธอดจะไม่ใช่อินสแตนซ์ของ Circle ตามที่คาดไว้ (จริง ๆ แล้วมันจะเป็นwindowออบเจ็กต์ระดับโลก ในความเป็นจริงสิ่งนี้มักเกิดขึ้นเมื่อมีการใช้วิธีการและกำหนดให้กับsetTimeout, onclickหรือEventListenerในทั่วไป

ด้วยวิธีต้นแบบคุณต้องรวมการปิดสำหรับการมอบหมายดังกล่าวทุกครั้ง:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

หรือในอนาคต (หรือตอนนี้ถ้าคุณแฮ็ค Function.prototype) คุณสามารถทำได้ด้วยfunction.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

หากอินสแตนซ์ของคุณเสร็จสิ้นการปิดการผูกจะทำฟรีโดยการปิดตัวแปรอินสแตนซ์ (โดยปกติแล้วจะเรียกว่าthatหรือselfแม้ว่าโดยส่วนตัวแล้วฉันจะให้คำแนะนำกับหลังอย่างที่selfมีความหมายอื่นที่แตกต่างกันใน JavaScript) คุณจะไม่ได้รับข้อโต้แย้ง1, 1ในตัวอย่างข้างต้นโดยไม่เสียค่าใช้จ่ายดังนั้นคุณจะยังคงต้องมีการปิดอีกครั้งหรือbind()ถ้าคุณต้องการทำเช่นนั้น

มีวิธีการปิดแบบต่างๆมากมายเช่นกัน คุณอาจต้องการละเว้นthisอย่างสมบูรณ์โดยสร้างใหม่thatและส่งคืนแทนที่จะใช้newโอเปอเรเตอร์:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

วิธีไหน“ เหมาะสม”? ทั้งสอง ข้อไหนดีที่สุด? ขึ้นอยู่กับสถานการณ์ของคุณ FWIW ฉันมีแนวโน้มที่จะสร้างต้นแบบสำหรับการสืบทอด JavaScript จริงเมื่อฉันทำสิ่งที่ OO อย่างยิ่งและปิดสำหรับผลหน้าทิ้งง่าย

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

[สิ่งนี้เป็นส่วนที่ 94 ของเพราะเหตุใด JavaScript จึงไม่ใช่ภาษาโปรแกรมโปรดของฉัน]


13
ดีมากทีละขั้นตอนจาก def "คลาส" เพื่อการเริ่มต้นวัตถุ newและสัมผัสที่ดีในอ้อม
Crescent Fresh

8
ดูเหมือนว่า JavaScript ไม่ใช่ภาษาที่คุณชื่นชอบเพราะคุณต้องการใช้มันราวกับว่ามันมีคลาส
Jonathan Feinberg

59
แน่นอนฉันก็ทำเช่นนั้นทุกคน: โมเดลคลาสและอินสแตนซ์เป็นโมเดลที่เป็นธรรมชาติมากขึ้นสำหรับโปรแกรมเมอร์ปัญหาส่วนใหญ่ที่เผชิญอยู่ทุกวันนี้ ฉันยอมรับว่าในทางทฤษฎีแล้วการสืบทอดโดยใช้ต้นแบบสามารถเสนอวิธีการทำงานที่ยืดหยุ่นมากขึ้น แต่ JavaScript ทั้งหมดไม่ได้ให้สัญญา ระบบฟังก์ชั่นคอนสตรัคเตอร์ที่สร้างความไม่น่าเชื่อถือทำให้เราแย่ที่สุดในโลกทั้งสองทำให้การสืบทอดคลาสคล้ายยากขณะที่ไม่มีความยืดหยุ่นหรือความเรียบง่ายแบบต้นแบบ ในระยะสั้นมันเป็นปู
bobince

4
บ๊อบฉันคิดว่านี่เป็นคำตอบที่ยอดเยี่ยม - ฉันต่อสู้กับรูปแบบทั้งสองนี้มาระยะหนึ่งแล้วและฉันคิดว่าคุณเขียนโค้ดที่กระชับกว่า Resig และอธิบายด้วยความเข้าใจลึกซึ้งกว่า Crockford ไม่มีคำชมสูงกว่านี้ที่ฉันจะนึกได้ ....
James Westgate

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

90

ฉันใช้รูปแบบนี้ค่อนข้างบ่อย - ฉันพบว่ามันให้ความยืดหยุ่นกับฉันค่อนข้างมากเมื่อฉันต้องการ ในการใช้งานมันค่อนข้างคล้ายกับคลาสสไตล์ Java

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

สิ่งนี้ใช้ฟังก์ชั่นที่ไม่ระบุชื่อที่เรียกว่าการสร้างส่งคืนฟังก์ชันตัวสร้างใหม่ เนื่องจากฟังก์ชันที่ไม่ระบุชื่อถูกเรียกเพียงครั้งเดียวคุณสามารถสร้างตัวแปรสแตติกส่วนตัวในนั้นได้ ฟังก์ชั่นคอนสตรัคเป็นพื้นวัตถุ Javascript มาตรฐาน - คุณกำหนดคุณลักษณะส่วนตัวภายในของมันและแอตทริบิวต์สาธารณะจะถูกแนบกับthisตัวแปร

โดยทั่วไปวิธีการนี้จะรวมวิธีการของ Crockford กับวัตถุ Javascript มาตรฐานเพื่อสร้างคลาสที่มีประสิทธิภาพยิ่งขึ้น

คุณสามารถใช้งานได้เหมือนกับที่คุณทำกับวัตถุ Javascript อื่น ๆ :

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

4
นั่นดูน่าสนใจเพราะค่อนข้างใกล้กับ "บ้านหญ้า" ของฉันซึ่งก็คือ C # ฉันยังคิดว่าฉันเริ่มเข้าใจว่าทำไม privateStaticVariable เป็นส่วนตัวจริงๆ (ตามที่กำหนดไว้ในขอบเขตของฟังก์ชั่นและยังคงมีชีวิตอยู่ตราบใดที่มีการอ้างอิงถึงมัน)
Michael Stum

เนื่องจากไม่ได้ใช้งานthisจึงจำเป็นต้องมีการสร้างอินสแตนซ์ด้วยnew?
Jordan Parmer

จริงๆแล้วthis จะใช้ในconstructorฟังก์ชั่นซึ่งกลายเป็นFooตัวอย่าง
ShZ

4
ปัญหาที่นี่คือวัตถุทุกชิ้นจะได้รับสำเนาของฟังก์ชั่นส่วนตัวและสาธารณะทั้งหมด
virtualnobi

2
@virtualnobi: รูปแบบนี้ไม่ได้ป้องกันคุณจากการเขียนวิธีการ constructor.prototype.myMethod = function () { ... }protytpe:
Nicolas Le Thierry d'Ennequin

25

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

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

ใน Javascript ฟังก์ชั่นเป็นวัตถุและสามารถใช้ในการสร้างวัตถุออกมาพร้อมกับผู้ประกอบการใหม่ ตามแบบแผนฟังก์ชั่นที่ตั้งใจจะใช้เป็นตัวสร้างเริ่มต้นด้วยตัวพิมพ์ใหญ่ คุณมักจะเห็นสิ่งต่าง ๆ เช่น:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

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


5
เป็นฉันหรือฉันคิดว่า Crockford ไม่สมเหตุสมผลกับการทุบตีผู้ประกอบการใหม่ของเขาหรือไม่?
meder omuraliev

3
@meder: ไม่ใช่แค่คุณ อย่างน้อยฉันคิดว่าไม่มีอะไรผิดปกติกับผู้ให้บริการรายใหม่ และมีนัยnewในvar that = {};อย่างไรก็ตาม
Tim Down

17
Crockford เป็นชายชราบ้าๆบอ ๆ และฉันไม่เห็นด้วยกับเขามากมาย แต่อย่างน้อยเขาก็สนับสนุนการมองจาวาสคริปต์ที่สำคัญและมันก็คุ้มค่าที่จะฟังสิ่งที่เขาพูด
234 bobince

2
@bince: เห็นด้วย งานเขียนของเขาเกี่ยวกับการปิดเปิดตาของฉันไปยังสิ่งต่าง ๆ มากมายประมาณ 5 ปีที่ผ่านมาและเขาส่งเสริมวิธีการคิด
Tim Down

20
ฉันเห็นด้วยกับ Crockford ปัญหาเกี่ยวกับตัวดำเนินการใหม่คือ JavaScript จะทำให้บริบทของ "this" แตกต่างจากเมื่อเรียกใช้ฟังก์ชัน แม้จะมีการประชุมกรณีที่เหมาะสมมีปัญหาที่เกิดขึ้นบนฐานรหัสขนาดใหญ่เป็นนักพัฒนาลืมที่จะใช้ใหม่ลืมที่จะลงทุน ฯลฯ เพื่อเป็นประโยชน์คุณสามารถทำทุกสิ่งที่คุณต้องทำโดยไม่ต้องใช้คำหลักใหม่ - ทำไมใช้มันและ แนะนำจุดความล้มเหลวเพิ่มเติมในรหัสหรือไม่ JS เป็นภาษาต้นแบบไม่ใช่คลาสที่ใช้ แล้วทำไมเราถึงต้องการให้มันทำตัวเหมือนภาษาที่พิมพ์แบบคงที่? ฉันทำไม่ได้อย่างแน่นอน
Joshua Ramirez

13

เพื่อดำเนินการต่อจากคำตอบของ bobince

ใน es6 คุณสามารถสร้างได้แล้ว class

ดังนั้นตอนนี้คุณสามารถทำได้:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

ดังนั้นขยายเป็นวงกลม (เช่นเดียวกับคำตอบอื่น ๆ ) คุณสามารถทำได้:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

จบลงด้วยการทำความสะอาดเล็กน้อยใน es6 และอ่านง่ายขึ้นเล็กน้อย


นี่เป็นตัวอย่างที่ดีในการใช้งาน:


6

คุณสามารถทำได้ด้วยวิธีนี้โดยใช้โครงสร้าง:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

จากนั้น:

var counter1 = createCounter();
counter1.increaseBy(4);

6
ฉันไม่ชอบวิธีนั้นเพราะช่องว่างสำคัญ ส่วนโค้งหลังการส่งคืนต้องอยู่ในบรรทัดเดียวกันเพื่อให้เข้ากันได้กับเบราว์เซอร์
geowa4

5

อีกวิธีหนึ่งคือhttp://jsfiddle.net/nnUY4/ (ฉันไม่รู้ว่าการจัดการวัตถุประเภทนี้และฟังก์ชั่นการเปิดเผยตามรูปแบบเฉพาะใด ๆ )

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

4

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

นี่คือวัตถุ JavaScript ของวานิลลา:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

คุณอาจได้รับจำนวนมากจากการอ่านสิ่งที่Douglas Crockfordพูดเกี่ยวกับ JavaScript John Resigก็ยอดเยี่ยมเช่นกัน โชคดี!


1
เอ่อการปิดล้อมthisมีทุกอย่างที่เกี่ยวข้องกับ "การทำให้ขอบเขตถูกต้อง"
Roatin Marth

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

ฉันคิดว่าคุณทุกคนพูดสิ่งเดียวกันจริง ๆ self=thisแม้ว่าจะไม่บังคับthisให้คงอยู่ แต่อนุญาตให้ทำการ "กำหนด" ได้อย่างง่ายดายผ่านการปิด
Crescent Fresh

2
เหตุผลที่คุณทำ = นี่คือการให้ฟังก์ชันที่ซ้อนกันเข้าถึงขอบเขตของสิ่งนี้เนื่องจากมีอยู่ในฟังก์ชันตัวสร้าง เมื่อฟังก์ชั่นที่ซ้อนกันอยู่ภายในฟังก์ชั่นการสร้างขอบเขต "นี้" ของพวกเขาจะเปลี่ยนเป็นขอบเขตทั่วโลก
Joshua Ramirez

4

Closureเอนกประสงค์ Bobinceได้สรุปต้นแบบและวิธีการปิดอย่างดีเมื่อสร้างวัตถุ อย่างไรก็ตามคุณสามารถเลียนแบบบางส่วนของการOOPใช้การปิดในวิธีการเขียนโปรแกรมการทำงาน จำได้ว่าฟังก์ชั่นเป็นวัตถุใน JavaScript ; ดังนั้นใช้ฟังก์ชั่นเป็นวัตถุในวิธีที่แตกต่าง

นี่คือตัวอย่างของการปิด:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

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

อย่างไร?

สมมติว่าคุณต้องการคำนวณภาษีมูลค่าเพิ่มของบางรายการ VAT มีแนวโน้มที่จะคงที่ตลอดอายุการใช้งานของแอปพลิเคชัน วิธีหนึ่งใน OOP (รหัสเทียม):

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

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

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

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

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures


3

การสร้างวัตถุ

วิธีที่ง่ายที่สุดในการสร้างวัตถุใน JavaScript คือการใช้ไวยากรณ์ต่อไปนี้:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

วิธีนี้ใช้งานได้ดีในการจัดเก็บข้อมูลในแบบที่มีโครงสร้าง

สำหรับกรณีการใช้ที่ซับซ้อนมากขึ้นอย่างไรก็ตามมักจะเป็นการดีกว่าที่จะสร้างอินสแตนซ์ของฟังก์ชัน:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

สิ่งนี้จะช่วยให้คุณสร้างวัตถุหลายรายการที่ใช้ "พิมพ์เขียว" เดียวกันซึ่งคล้ายกับวิธีที่คุณใช้คลาสเช่น ชวา

อย่างไรก็ตามยังสามารถทำได้อย่างมีประสิทธิภาพมากขึ้นโดยใช้ต้นแบบ

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

ในกรณีของเรามันเป็นการสมควรที่จะย้ายวิธีfไปที่ต้นแบบ:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

มรดก

วิธีที่ง่าย แต่มีประสิทธิภาพในการรับมรดกใน JavaScript คือใช้สองซับต่อไปนี้:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

นั่นคล้ายกับการทำสิ่งนี้:

B.prototype = new A();

ความแตกต่างที่สำคัญระหว่างทั้งสองคือตัวสร้างของAจะไม่ทำงานเมื่อใช้Object.createซึ่งง่ายกว่าและคล้ายกับการสืบทอดตามคลาส

คุณสามารถเลือกที่จะเรียกใช้นวกรรมิกของAเมื่อสร้างอินสแตนซ์ใหม่Bด้วยการเพิ่มเพิ่มลงในนวกรรมิกของB:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

หากคุณต้องการส่งผ่านอาร์กิวเมนต์ทั้งหมดBไปยังAคุณสามารถใช้Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

ถ้าคุณต้องการที่จะผสมวัตถุอื่นเข้าไปในห่วงโซ่การสร้างของBคุณสามารถรวมObject.createกับObject.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

การสาธิต

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


บันทึก

Object.createสามารถใช้ได้อย่างปลอดภัยในทุกเบราว์เซอร์ที่ทันสมัยรวมถึง IE9 + Object.assignไม่สามารถใช้งานได้กับ IE หรือเบราว์เซอร์มือถือบางรุ่น ขอแนะนำให้polyfill Object.createและ / หรือObject.assignถ้าคุณต้องการใช้และสนับสนุนเบราว์เซอร์ที่ไม่ได้ใช้

คุณสามารถค้นหา polyfill สำหรับObject.create ที่นี่ และหนึ่งสำหรับที่นี่Object.assign


0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);

4
สิ่งนี้เพิ่มอะไรให้กับคำตอบมากมายที่เสนอไปแล้ว?
blm

ฉันชอบคำตอบนี้เนื่องจากรัดกุมและแสดงสามส่วนของการใช้งาน: 1) กำหนดวัตถุ 2) สร้างอินสแตนซ์ของวัตถุ 3) ใช้อินสแตนซ์ - มันแสดงให้เห็นทั้งหมดได้อย่างรวดเร็วแทนที่จะมีการแยกวิเคราะห์ ผ่านคำตอบ verbose ทั้งหมดข้างต้น (ซึ่งแน่นอนว่าเป็นคำตอบที่ดีมากพร้อมรายละเอียดที่เกี่ยวข้องทั้งหมดที่เราต้องการ) - เป็นบทสรุปง่าย ๆ ที่นี่
G-Man

0

นอกจากนี้ยังมีคำตอบที่ได้รับการยอมรับจากปี 2009 ถ้าคุณสามารถสามารถกำหนดเป้าหมายเบราว์เซอร์ที่ทันสมัยหนึ่งสามารถทำให้การใช้งานของObject.defineProperty

กระบวนการ Object.defineProperty () วิธีการกำหนดคุณสมบัติใหม่โดยตรงบนวัตถุหรือปรับเปลี่ยนคุณสมบัติที่มีอยู่บนวัตถุและส่งกลับวัตถุ ที่มา: Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

หากต้องการดูเดสก์ทอปและรายการที่เข้ากันมือถือดูเบราว์เซอร์รายการความเข้ากันได้ของ Mozilla ใช่ IE9 + รองรับเช่นเดียวกับ Safari mobile


0

คุณสามารถลองสิ่งนี้

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();

0
รูปแบบที่ให้บริการฉันได้ดี
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

ครั้งแรกที่คุณอาจมีการเปลี่ยนแปลงการตั้งค่าของคุณวิธีการเพิ่มอินสแตนซ์แทนคอนสตรัคฯprototypeวัตถุ ฉันมักจะประกาศวิธีการภายในของตัวสร้างเพราะฉันใช้การหักหลัง Constructorบ่อยครั้งมากเพื่อจุดประสงค์เกี่ยวกับการสืบทอดและการตกแต่ง

นี่คือวิธีที่ฉันตัดสินใจว่าการประกาศใดที่จะเขียน:

  • อย่าประกาศวิธีการโดยตรงบนวัตถุบริบท ( this)
  • ให้varการประกาศมีความสำคัญเหนือfunctionการประกาศ
  • ให้คำนำหน้าสำคัญเหนือวัตถุ ( {}และ[])
  • ให้publicการประกาศมีความสำคัญเหนือprivateการประกาศ
  • ชอบFunction.prototype.bindมากกว่าthus, self, vm,etc
  • หลีกเลี่ยงการประกาศคลาสภายในคลาสอื่นยกเว้น:
    • มันควรจะชัดเจนว่าทั้งสองแยกกันไม่ออก
    • ชั้นในใช้รูปแบบคำสั่ง
    • ชั้นในใช้รูปแบบซิงเกิล
    • ชั้นในใช้รูปแบบสถานะ
    • The Inner Class ใช้รูปแบบการออกแบบอื่นที่รับประกันสิ่งนี้
  • ส่งคืนthisจากภายในขอบเขตคำศัพท์ของพื้นที่ปิดเสมอ

นี่คือสาเหตุที่ความช่วยเหลือเหล่านี้:

ตัวสร้างการไฮแจ็ค
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
การออกแบบรูปแบบ
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
รูปแบบการออกแบบ
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

ที่คุณสามารถดูผมมีความต้องการไม่มีthusตั้งแต่ผมต้องการFunction.prototype.bind(หรือ.callหรือ.apply) thusมากกว่า ในSingletonชั้นเรียนของเราเราไม่ได้ตั้งชื่อthusเพราะINSTANCEบ่งบอกข้อมูลเพิ่มเติม สำหรับModelเรากลับมาthisเพื่อให้เราสามารถเรียกใช้ Constructor ที่ใช้.callเพื่อส่งคืนอินสแตนซ์ที่เราส่งเข้าไป ซ้ำซ้อนเรากำหนดให้ตัวแปรupdatedแม้ว่าจะมีประโยชน์ในสถานการณ์อื่น ๆ

ควบคู่ไปกับฉันต้องการสร้างตัวอักษรวัตถุโดยใช้newคำหลักมากกว่า {วงเล็บ}:

ที่ต้องการ
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
ไม่ต้องการ
var klass = Super.apply({
    override: x
});

อย่างที่คุณเห็นหลังไม่มีความสามารถในการแทนที่คุณสมบัติ "แทนที่" ของ Superclass

ถ้าฉันเพิ่มวิธีการในprototypeวัตถุของ Class ฉันชอบวัตถุตามตัวอักษร - มีหรือไม่มีการใช้newคำหลัก:

ที่ต้องการ
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
ไม่ต้องการ
Klass.prototype.method = function m() {...};

0

ฉันต้องการพูดถึงว่าเราสามารถใช้ชื่อเรื่องหรือสตริงเพื่อประกาศวัตถุ
มีวิธีที่แตกต่างกันในการโทรแต่ละประเภท ดูด้านล่าง:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));


-1

โดยพื้นฐานแล้วไม่มีแนวคิดของคลาสใน JS ดังนั้นเราจึงใช้ฟังก์ชั่นเป็นตัวสร้างคลาสที่เกี่ยวข้องกับรูปแบบการออกแบบที่มีอยู่

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

จนถึงตอนนี้ JS ไม่มีเงื่อนงำที่คุณต้องการสร้างวัตถุดังนั้นนี่คือคำค้นหาใหม่

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ref: Professional JS สำหรับนักพัฒนาเว็บ - Nik Z


ยอมรับ Downvote: ด้วยเหตุผลที่ถูกต้องจะมีข้อมูลมากขึ้นและจะมีโอกาสในการปรับปรุง
Airwind711

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