Javascript เมื่อใดควรใช้ต้นแบบ


93

ฉันต้องการทราบว่าเมื่อใดจึงเหมาะสมที่จะใช้เมธอดต้นแบบใน js ควรใช้เสมอหรือไม่? หรือมีบางกรณีที่ไม่แนะนำให้ใช้และ / หรือถูกลงโทษด้านประสิทธิภาพ?

ในการค้นหารอบ ๆ ไซต์นี้ด้วยวิธีการทั่วไปสำหรับการกำหนดเนมสเปซใน js ดูเหมือนว่าส่วนใหญ่จะใช้การใช้งานที่ไม่ใช่ต้นแบบ: เพียงแค่ใช้อ็อบเจกต์หรืออ็อบเจ็กต์ฟังก์ชันเพื่อห่อหุ้มเนมสเปซ

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

คำตอบ:


135

ต้นแบบเป็นการเพิ่มประสิทธิภาพ

ตัวอย่างที่ดีในการใช้ไลบรารี jQuery ทุกครั้งที่คุณได้รับวัตถุ jQuery โดยใช้$('.someClass')วัตถุนั้นจะมี "วิธีการ" มากมาย ไลบรารีสามารถบรรลุสิ่งนั้นได้โดยส่งคืนวัตถุ:

return {
   show: function() { ... },
   hide: function() { ... },
   css: function() { ... },
   animate: function() { ... },
   // etc...
};

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

แต่วิธีการเหล่านั้นถูกกำหนดบนต้นแบบและวัตถุ jQuery ทั้งหมด "สืบทอด" ต้นแบบนั้นเพื่อให้ได้รับวิธีการเหล่านั้นทั้งหมดโดยมีต้นทุนรันไทม์น้อยมาก

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

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

เพื่อให้คุณสะดวกสามารถสร้างวัตถุที่มีต้นแบบที่กำหนด ECMAScript 5 Object.createรวมถึงฟังก์ชั่นมาตรฐาน เวอร์ชันที่เรียบง่ายขึ้นมากจะมีลักษณะดังนี้:

Object.create = function(prototype) {
    var Type = function () {};
    Type.prototype = prototype;
    return new Type();
};

newมันใช้เวลาเพียงดูแลความเจ็บปวดจากการเขียนฟังก์ชั่นคอนสตรัคแล้วเรียกมันว่าด้วย

คุณจะหลีกเลี่ยงต้นแบบเมื่อใด

การเปรียบเทียบที่มีประโยชน์คือภาษา OO ยอดนิยมเช่น Java และ C # สิ่งเหล่านี้สนับสนุนการถ่ายทอดทางพันธุกรรมสองประเภท:

  • การสืบทอดอินเทอร์เฟซโดยที่คุณimplementเป็นinterfaceเช่นนั้นคลาสจะจัดเตรียมการนำไปใช้งานเฉพาะสำหรับสมาชิกทุกคนของอินเทอร์เฟซ
  • การดำเนินการรับมรดกที่คุณที่ให้การใช้งานเริ่มต้นของวิธีการบางอย่างextendclass

ใน JavaScript การสืบทอดต้นแบบคือการสืบทอดการนำไปใช้งานประเภทหนึ่ง ดังนั้นในสถานการณ์เหล่านั้นที่ (ใน C # หรือ Java) คุณจะได้รับมาจากคลาสพื้นฐานเพื่อให้ได้พฤติกรรมเริ่มต้นซึ่งคุณจะทำการปรับเปลี่ยนเล็กน้อยผ่านการแทนที่จากนั้นใน JavaScript การสืบทอดต้นแบบก็สมเหตุสมผล

อย่างไรก็ตามหากคุณอยู่ในสถานการณ์ที่คุณจะต้องใช้อินเทอร์เฟซใน C # หรือ Java คุณก็ไม่จำเป็นต้องมีคุณสมบัติภาษาใด ๆ ใน JavaScript ไม่จำเป็นต้องประกาศสิ่งที่แสดงถึงอินเทอร์เฟซอย่างชัดเจนและไม่จำเป็นต้องทำเครื่องหมายออบเจ็กต์ว่า "ใช้งาน" อินเทอร์เฟซนั้น:

var duck = {
    quack: function() { ... }
};

duck.quack(); // we're satisfied it's a duck!

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

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


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

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

3
1.ฉันหมายความว่าหากมีอินสแตนซ์เป็ดประเภทหนึ่งจำนวนมากการปรับเปลี่ยนตัวอย่างเพื่อให้quackฟังก์ชันนั้นอยู่ในต้นแบบซึ่งมีการเชื่อมโยงอินสแตนซ์เป็ดจำนวนมาก 2.ไวยากรณ์ตัวอักษรของวัตถุ{ ... }สร้างอินสแตนซ์ (ไม่จำเป็นต้องใช้newกับมัน) 3. การเรียกใช้ฟังก์ชัน JS ทำให้สร้างออบเจ็กต์อย่างน้อยหนึ่งรายการในหน่วยความจำ - เรียกว่าargumentsอ็อบเจ็กต์และเก็บอาร์กิวเมนต์ที่ส่งผ่านในการโทร: developer.mozilla.org/en/JavaScript/Reference/…
Daniel Earwicker

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

11
+1 การเปรียบเทียบกับ jQuery เป็นคำอธิบายที่ชัดเจนและกระชับเป็นครั้งแรกว่าเมื่อใดและทำไมต้องใช้ต้นแบบที่ฉันได้อ่าน ขอบคุณมาก.
GFoley83

46

คุณควรใช้ต้นแบบหากคุณต้องการประกาศวิธีการ "ไม่คงที่" ของวัตถุ

var myObject = function () {

};

myObject.prototype.getA = function (){
  alert("A");
};

myObject.getB = function (){
  alert("B");
};

myObject.getB();  // This works fine

myObject.getA();  // Error!

var myPrototypeCopy = new myObject();
myPrototypeCopy.getA();  // This works, too.

@keatsKelleher แต่เราสามารถสร้างเมธอดแบบไม่คงที่สำหรับอ็อบเจกต์ได้โดยกำหนดวิธีการภายในฟังก์ชันคอนสตรัคเตอร์โดยใช้thisตัวอย่างthis.getA = function(){alert("A")}ใช่ไหม
Amr Labib

17

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

สมมติว่าคุณมีCar()คลาสพื้นฐาน/ วัตถุ

function Car() {
    // do some car stuff
}

จากนั้นคุณจะสร้างหลายCar()อินสแตนซ์

var volvo = new Car(),
    saab = new Car();

ตอนนี้คุณรู้แล้วว่ารถแต่ละคันจะต้องขับเปิด ฯลฯ แทนที่จะแนบวิธีการโดยตรงกับCar()คลาส (ซึ่งใช้หน่วยความจำต่อแต่ละอินสแตนซ์ที่สร้างขึ้น) คุณสามารถแนบวิธีการกับต้นแบบแทนได้ (สร้างเฉพาะเมธอดเท่านั้น หนึ่งครั้ง) ดังนั้นการให้การเข้าถึงวิธีการเหล่านั้นทั้งใหม่volvoและsaab.

// just mapping for less typing
Car.fn = Car.prototype;

Car.fn.drive = function () {
    console.log("they see me rollin'");
};
Car.fn.honk = function () {
    console.log("HONK!!!");
}

volvo.honk();
// => HONK!!!
saab.drive();
// => they see me rollin'

2
อันนี้ไม่ถูกต้อง volvo.honk () จะไม่ทำงานเนื่องจากคุณเปลี่ยนวัตถุต้นแบบทั้งหมดไม่ได้ขยายออก หากคุณจะทำสิ่งนี้มันจะได้ผลอย่างที่คุณคาดหวัง: Car.prototype.honk = function () {console.log ('HONK');} volvo.honk (); // 'HONK'
29

1
@ 29er - วิธีที่ฉันเขียนตัวอย่างนี้คุณถูกต้อง คำสั่งไม่สำคัญ ถ้าฉันจะเก็บตัวอย่างนี้เป็นที่Car.prototype = { ... }จะต้องมาก่อนที่จะเรียกnew Car()ดังแสดงใน jsfiddle นี้: jsfiddle.net/mxacA สำหรับอาร์กิวเมนต์ของคุณนี้จะเป็นวิธีที่ถูกต้องในการทำมันjsfiddle.net/Embnp สิ่งที่ตลกคือฉันจำไม่ได้ว่าตอบคำถามนี้ =)
hellatan

@hellatan คุณสามารถแก้ไขได้โดยการตั้งค่าตัวสร้าง: Car to เนื่องจากคุณเขียนทับคุณสมบัติต้นแบบด้วยตัวอักษรของวัตถุ
Josh Bedo

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

12

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

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

ตอนนี้สาเหตุที่คุณทำสิ่งเหล่านี้ส่วนใหญ่เป็นหน้าที่ของการออกแบบแอปพลิเคชันของคุณเองและประเภทของสิ่งที่คุณต้องทำในโค้ดฝั่งไคลเอ็นต์ (เรื่องราวที่แตกต่างกันทั้งหมดจะเป็นโค้ดภายในเซิร์ฟเวอร์ง่ายกว่ามากที่จะจินตนาการว่าทำโค้ด "OO" ขนาดใหญ่ที่นั่น)


ดังนั้นเมื่อฉันสร้างอินสแตนซ์อ็อบเจ็กต์ใหม่ด้วยเมธอดต้นแบบ (ผ่านคีย์เวิร์ดใหม่) อ็อบเจ็กต์นั้นจะไม่ได้รับสำเนาใหม่ของแต่ละฟังก์ชัน (เพียงแค่พอยน์เตอร์)? ในกรณีนี้ทำไมคุณไม่ต้องการใช้ต้นแบบ?
opl

like @marcel, d'oh ... =)
hellatan

@opi ใช่คุณพูดถูก - ไม่มีการทำสำเนา ในทางกลับกันสัญลักษณ์ (ชื่อคุณสมบัติ) บนอ็อบเจ็กต์ต้นแบบเป็นเพียงการจัดเรียง "ตรงนั้น" ตามธรรมชาติเป็นส่วนเสมือนของอ็อบเจ็กต์อินสแตนซ์แต่ละชิ้น เหตุผลเดียวที่ผู้คนไม่อยากรบกวนนั่นก็คือกรณีที่วัตถุมีอายุสั้นและแตกต่างกันหรือมี "พฤติกรรม" ที่จะแบ่งปันไม่มากนัก
Pointy

3

ถ้าฉันอธิบายในคำศัพท์ตามคลาสแล้ว Person คือคลาส walk () คือ Prototype method ดังนั้น walk () จะมีอยู่หลังจากที่คุณสร้างอินสแตนซ์วัตถุใหม่ด้วยสิ่งนี้เท่านั้น

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

ในขณะที่แบบคงที่ไม่ใช่ความช่วยเหลือที่ดีเยี่ยมในสถานการณ์ดังกล่าว

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

// its instance method and can access objects data data 
Person.prototype.walk = function(){
alert("person has started walking.");
}
// its like static method
Person.ProcessPerson = function(Person p){
alert("Persons name is = " + p.name);
}

var userOne = new Person();
var userTwo = new Person();

//Call instance methods
userOne.walk();

//Call static methods
Person.ProcessPerson(userTwo);

ดังนั้นด้วยวิธีนี้จึงเหมือนวิธีอินสแตนซ์มากกว่า แนวทางของวัตถุก็เหมือนกับ Static method

https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript

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