創建對象方式
工廠模式:用于抽象創建特定對象的過程。可以解決創建多個類似對象的問題,但沒有解決對象標識問題。(即新創建的對象是什么類型)
function createPerson(name, age, job) { let o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { console.log(this.name); }; return o; } let person1 = createPerson("Nicholas", 29, "Software Engineer"); let person2 = createPerson("Greg", 27, "Doctor");
構造函數模式:用于創建特定類型對象。構造函數名稱的首字母都是要大寫的,用來區分非構造函數。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function() { console.log(this.name); }; } let person1 = new Person("Nicholas", 29, "Software Engineer"); let person2 = new Person("Greg", 27, "Doctor"); person1.sayName(); // Nicholas person2.sayName(); // Greg
原型模式:每個函數都會創建一個prototype屬性,這個屬性是一個對象,包含應該由特定引用類型的實例共享的屬性和方法。使用原型對象的好處是,在它上面定義的屬性和方法可以被對象實例共享。
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function() { console.log(this.name); }; let person1 = new Person(); person1.sayName(); // "Nicholas" let person2 = new Person(); person2.sayName(); // "Nicholas" console.log(person1.sayName == person2.sayName); // true
繼承的方式:接口繼承和實現繼承。接口繼承在 ECMAScript 中是不可能的,因為函數沒有簽名。實現繼承是 ECMAScript 唯一支持的繼承方式,而這主要是通過原型鏈實現的。
原型鏈?
其基本思想就是通過原型繼承多個引用類型的屬性和方法。function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } // 繼承 SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; }; let instance = new SubType(); console.log(instance.getSuperValue()); // true
原型鏈雖然是實現繼承的強大工具,但它也有問題。問題一:出現在原型中包含引用值的時候。前面在談到原型的問題時也提到過,原型中包含的引用值會在所有實例間共享,這也是為什么屬性通常會在構造函數中定義而不會定義在原型上的原因。在使用原型實現繼承時,原型實際上變成了另一個類型的實例。這意味著原先的實例屬性搖身一變成為了原型屬性。問題二:子類型在實例化時不能給父類型的構造函數傳參。function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() {} // 繼承 SuperType SubType.prototype = new SuperType(); let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green,black"
解決原型包含引用值導致的繼承問題
盜用構造函數:在子類構造函數中調用父類構造函數。優點可以在子類構造函數中向父類構造函數傳參。缺點:必須在構造函數中定義方法,因此函數不能重用。
function SuperType(name){ this.name = name; } function SubType() { // 繼承 SuperType 并傳參SuperType.call(this, "Nicholas"); // 實例屬性this.age = 29; } let instance = new SubType(); console.log(instance.name); // "Nicholas"; console.log(instance.age); // 29
?組合繼承:綜合了原型鏈和盜用構造函數,使用原型鏈繼承原型上的屬性和方法,通過盜用構造函數繼承實例屬性。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); }; function SubType(name, age){ // 繼承屬性SuperType.call(this, name); this.age = age; } // 繼承方法 SubType.prototype = new SuperType(); SubType.prototype.sayAge = function() { console.log(this.age); }; let instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "Nicholas"; instance1.sayAge(); // 29 let instance2 = new SubType("Greg", 27); console.log(instance2.colors); // "red,blue,green" instance2.sayName(); // "Greg"; instance2.sayAge(); // 27
原型式繼承
ES5通過增加 Object.create()方法將原型式繼承的概念規范化了,這個方法接收兩個
參數:作為新對象原型的對象,以及給新對象定義額外屬性的對象(第二個可選)。原型式繼承非常適合不需要單獨創建構造函數,但仍然需要在對象間共享信息的場合。let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; let anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); let yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
寄生式繼承
function createAnother(original){ let clone = object(original); // 通過調用函數創建一個新對象clone.sayHi = function() { // 以某種方式增強這個對象console.log("hi"); }; return clone; // 返回這個對象 }
寄生式組合繼承
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); }; function SubType(name, age){ SuperType.call(this, name); // 第二次調用 SuperType() this.age = age; } SubType.prototype = new SuperType(); // 第一次調用 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); };
寄生式組合繼承算是引用類型繼承的最佳模式!