ในขณะที่คนจำนวนมากที่นี่บอกว่าไม่มีวิธีที่ดีที่สุดสำหรับการสร้างวัตถุ แต่มีเหตุผลว่าทำไมมีหลายวิธีในการสร้างวัตถุใน JavaScript ในปี 2019 และสิ่งนี้เกี่ยวข้องกับความคืบหน้าของ JavaScript ในการทำซ้ำที่แตกต่างกัน ของ EcmaScript ออกมาตั้งแต่ปี 1997
ก่อน ECMAScript 5 มีเพียงสองวิธีในการสร้างออบเจ็กต์: ฟังก์ชันคอนสตรัคเตอร์หรือสัญกรณ์สัญพจน์ (ทางเลือกที่ดีกว่าสำหรับออบเจ็กต์ใหม่) ด้วยสัญกรณ์ฟังก์ชั่นคอนสตรัคคุณสร้างวัตถุที่สามารถยกตัวอย่างเป็นหลายอินสแตนซ์ (ด้วยคำหลักใหม่) ในขณะที่สัญกรณ์ตัวอักษรส่งวัตถุเดียวเช่นเดียว
// constructor function
function Person() {};
// literal notation
var Person = {};
ไม่ว่าคุณจะใช้วิธีใดวัตถุ JavaScript เป็นคุณสมบัติของคู่ค่าคีย์:
// Method 1: dot notation
obj.firstName = 'Bob';
// Method 2: bracket notation. With bracket notation, you can use invalid characters for a javascript identifier.
obj['lastName'] = 'Smith';
// Method 3: Object.defineProperty
Object.defineProperty(obj, 'firstName', {
value: 'Bob',
writable: true,
configurable: true,
enumerable: false
})
// Method 4: Object.defineProperties
Object.defineProperties(obj, {
firstName: {
value: 'Bob',
writable: true
},
lastName: {
value: 'Smith',
writable: false
}
});
ในรุ่นแรก ๆ ของ JavaScript วิธีเดียวที่จะเลียนแบบการสืบทอดคลาสได้คือการใช้ฟังก์ชันตัวสร้าง ฟังก์ชั่นคอนสตรัคเป็นฟังก์ชั่นพิเศษที่ถูกเรียกด้วยคำหลัก 'ใหม่' โดยการประชุมตัวระบุฟังก์ชั่นเป็นตัวพิมพ์ใหญ่มันไม่จำเป็น ภายใน Constructor เราอ้างถึงคำสำคัญ 'this' เพื่อเพิ่มคุณสมบัติให้กับวัตถุที่ฟังก์ชัน Constructor สร้างขึ้นโดยปริยาย ฟังก์ชั่นคอนสตรัคส่งกลับวัตถุใหม่ที่มีคุณสมบัติที่มีประชากรกลับไปที่ฟังก์ชั่นการโทรโดยปริยายเว้นแต่คุณจะใช้คำหลักกลับมาอย่างชัดเจนและส่งกลับอย่างอื่น
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.sayName = function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var bob = new Person("Bob", "Smith");
bob instanceOf Person // true
มีปัญหากับเมธอด sayName โดยทั่วไปในภาษาเขียนโปรแกรมเชิงวัตถุคุณใช้คลาสเป็นโรงงานเพื่อสร้างวัตถุ แต่ละวัตถุจะมีตัวแปรอินสแตนซ์ของตัวเอง แต่มันจะมีตัวชี้ไปยังวิธีการที่กำหนดไว้ในพิมพ์เขียวระดับ น่าเสียดายที่เมื่อใช้ฟังก์ชันคอนสตรัคเตอร์ของ JavaScript ทุกครั้งที่เรียกมันจะกำหนดคุณสมบัติใหม่ของชื่อบนวัตถุที่สร้างขึ้นใหม่ ดังนั้นแต่ละวัตถุจะมีคุณสมบัติชื่อเฉพาะของตัวเอง สิ่งนี้จะใช้ทรัพยากรหน่วยความจำเพิ่มเติม
นอกเหนือจากทรัพยากรหน่วยความจำที่เพิ่มขึ้นการกำหนดวิธีการภายในฟังก์ชั่นการสร้างกำจัดความเป็นไปได้ของการสืบทอด อีกครั้งวิธีจะถูกกำหนดเป็นคุณสมบัติในวัตถุที่สร้างขึ้นใหม่และไม่มีวัตถุอื่นดังนั้นการสืบทอดไม่สามารถทำงานได้ ดังนั้น JavaScript จึงจัดเตรียมเชนต้นแบบเป็นรูปแบบของการสืบทอดทำให้ JavaScript เป็นภาษาต้นแบบ
หากคุณมีพาเรนต์และพาเรนต์ใช้คุณสมบัติหลายอย่างร่วมกันของลูกลูกก็ควรสืบทอดคุณสมบัติเหล่านั้น ก่อน ES5 สำเร็จได้ดังนี้:
function Parent(eyeColor, hairColor) {
this.eyeColor = eyeColor;
this.hairColor = hairColor;
}
Parent.prototype.getEyeColor = function() {
console.log('has ' + this.eyeColor);
}
Parent.prototype.getHairColor = function() {
console.log('has ' + this.hairColor);
}
function Child(firstName, lastName) {
Parent.call(this, arguments[2], arguments[3]);
this.firstName = firstName;
this.lastName = lastName;
}
Child.prototype = Parent.prototype;
var child = new Child('Bob', 'Smith', 'blue', 'blonde');
child.getEyeColor(); // has blue eyes
child.getHairColor(); // has blonde hair
วิธีที่เราใช้โซ่ต้นแบบด้านบนมีมุมแปลกประหลาด เนื่องจากต้นแบบเป็นลิงค์สดโดยการเปลี่ยนคุณสมบัติของวัตถุหนึ่งชิ้นในห่วงโซ่ต้นแบบคุณจะเปลี่ยนคุณสมบัติเดียวกันของวัตถุอื่นเช่นกัน เห็นได้ชัดว่าการเปลี่ยนวิธีการสืบทอดของเด็กไม่ควรเปลี่ยนวิธีการของผู้ปกครอง Object.create แก้ไขปัญหานี้โดยใช้ polyfill ดังนั้นด้วย Object.create คุณสามารถปรับเปลี่ยนคุณสมบัติของเด็กได้อย่างปลอดภัยในเชนต้นแบบโดยไม่ส่งผลกระทบต่อคุณสมบัติเดียวกันของผู้ปกครองในเชนต้นแบบ
ECMAScript 5 แนะนำ Object.create เพื่อแก้ไขข้อผิดพลาดดังกล่าวในฟังก์ชันตัวสร้างสำหรับการสร้างวัตถุ กระบวนการ Object.create () วิธีการสร้างวัตถุใหม่โดยใช้วัตถุที่มีอยู่เป็นต้นแบบของวัตถุที่สร้างขึ้นใหม่ เนื่องจากมีการสร้างวัตถุใหม่คุณไม่มีปัญหาในการแก้ไขคุณสมบัติลูกในเชนลูกโซ่ต้นแบบอีกต่อไปจะแก้ไขการอ้างอิงของผู้ปกครองกับคุณสมบัตินั้นในเชน
var bobSmith = {
firstName: "Bob",
lastName: "Smith",
sayName: function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var janeSmith = Object.create(bobSmith, {
firstName : { value: "Jane" }
})
console.log(bobSmith.sayName()); // My name is Bob Smith
console.log(janeSmith.sayName()); // My name is Jane Smith
janeSmith.__proto__ == bobSmith; // true
janeSmith instanceof bobSmith; // Uncaught TypeError: Right-hand side of 'instanceof' is not callable. Error occurs because bobSmith is not a constructor function.
ก่อน ES6 ต่อไปนี้เป็นรูปแบบการสร้างทั่วไปเพื่อใช้ตัวสร้างฟังก์ชันและ Object.create:
const View = function(element){
this.element = element;
}
View.prototype = {
getElement: function(){
this.element
}
}
const SubView = function(element){
View.call(this, element);
}
SubView.prototype = Object.create(View.prototype);
ตอนนี้ Object.create ควบคู่ไปกับฟังก์ชั่นคอนสตรัคเตอร์ถูกนำมาใช้กันอย่างแพร่หลายสำหรับการสร้างวัตถุและการสืบทอดใน JavaScript อย่างไรก็ตาม ES6 นำเสนอแนวคิดของคลาสซึ่งส่วนใหญ่เป็นประโยคเกี่ยวกับการสร้างรหัสผ่านสำหรับการสืบทอดจากต้นแบบที่มีอยู่ของ JavaScript ไวยากรณ์คลาสไม่แนะนำโมเดลการสืบทอดเชิงวัตถุใหม่ให้กับ JavaScript ดังนั้น JavaScript ยังคงเป็นภาษาต้นแบบ
คลาส ES6 ทำให้การสืบทอดง่ายขึ้นมาก เราไม่จำเป็นต้องคัดลอกฟังก์ชั่นต้นแบบของผู้ปกครองด้วยตนเองและรีเซ็ตตัวสร้างของคลาสลูก
// create parent class
class Person {
constructor (name) {
this.name = name;
}
}
// create child class and extend our parent class
class Boy extends Person {
constructor (name, color) {
// invoke our parent constructor function passing in any required parameters
super(name);
this.favoriteColor = color;
}
}
const boy = new Boy('bob', 'blue')
boy.favoriteColor; // blue
สรุปกลยุทธ์การสร้าง Object ใน JavaScript ทั้ง 5 นี้สอดคล้องกับวิวัฒนาการของมาตรฐาน EcmaScript