在JavaScript中,創建對象有多種方式,每種方式都有其優缺點。本文將介紹四種常見的對象創建模式:工廠模式、構造函數模式、原型模式和組合模式,并分析它們的特點以及如何優化。
1. 工廠模式
工廠模式是一種簡單的對象創建方式,它使用一個函數來封裝創建對象的細節。
function createPerson(name, age) {const obj = new Object();obj.name = name;obj.age = age;obj.sayName = function() {console.log(this.name);};return obj;
}const person1 = createPerson("Alice", 25);
const person2 = createPerson("Bob", 30);
1.1. 優點
1. 簡單易用,避免了重復代碼;
2. 可以創建多個相似對象;
1.2. 缺點
1. 創建的對象無法識別類型,因為都是Object類型;
2. 每個對象都有自己獨立的方法實例,造成內存浪費;
1.3. 優化方案
可以將方法移到工廠函數外部,減少內存消耗;
function sayName() {console.log(this.name);
}function createPerson(name, age) {const obj = new Object();obj.name = name;obj.age = age;obj.sayName = sayName;return obj;
}
2. 構造函數模式
構造函數模式使用new操作符來創建特定類型的對象。
function Person(name, age) {this.name = name;this.age = age;this.sayName = function() {console.log(this.name);};
}const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);
2.1. 優點
1.?可以識別對象類型,通過instanceof檢測;
2.?符合傳統的面向對象語言創建對象的方式;
2.2. 缺點
1.?每個方法都要在每個實例上重新創建一遍,造成內存浪費;
2.?如果方法很多,會占用大量不必要的內存;
2.3.?優化方案
將方法定義在構造函數外部
function Person(name, age) {this.name = name;this.age = age;this.sayName = sayName;
}function sayName() {console.log(this.name);
}
3. 原型模式
原型模式利用原型鏈來實現屬性和方法的共享。
function Person() {}Person.prototype.name = "Default";
Person.prototype.age = 0;
Person.prototype.sayName = function() {console.log(this.name);
};const person1 = new Person();
person1.name = "Alice";
person1.age = 25;const person2 = new Person();
person2.name = "Bob";
person2.age = 30;
3.1.? 優點
1.?所有對象實例共享原型上的屬性和方法,節省內存;
2.?可以在運行時動態修改原型,影響所有實例;
3.2.? 缺點
1.?所有實例默認取得相同的屬性值,不太靈活;
2.?對于包含引用類型值的屬性,如數組,一個實例修改會影響所有實例;
3.3.??優化方案
組合使用構造函數模式和原型模式
4. 組合模式(構造函數+原型)
組合模式是使用最廣泛、認同度最高的一種創建自定義類型的方法。
function Person(name, age) {// 實例屬性this.name = name;this.age = age;
}// 共享方法
Person.prototype = {constructor: Person,sayName: function() {console.log(this.name);}
};const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);
4.1.?優點
1. 每個實例有自己的屬性副本;
2. 所有實例共享方法引用,節省內存;
3. 可以向構造函數傳遞參數,靈活性高;
4. 是目前ECMAScript中使用最廣泛的方式;
4.2.?缺點
1.?構造函數和原型分開定義,可能讓代碼組織不夠清晰;
2.?對于習慣其他語言的開發者來說,這種模式可能不太直觀;
4.3.?優化方案
使用動態原型模式,將所有信息封裝在構造函數中。
function Person(name, age) {// 屬性this.name = name;this.age = age;// 方法(只在第一次調用構造函數時添加到原型)if (typeof this.sayName !== "function") {Person.prototype.sayName = function() {console.log(this.name);};}
}
注意:使用動態原型模式時,不能用對象字面量重寫原型。
?
function Person(name) {this.name = name;if (typeof this.getName != "function") {Person.prototype = {constructor: Person,getName: function () {console.log(this.name);}}}
}var person1 = new Person('Jack1');
var person2 = new Person('Jack2');// 報錯 并沒有該方法
person1.getName();// 注釋掉上面的代碼,這句是可以執行的。
person2.getName();
開始執行 var person1 = new Person('Jack1')
new 的執行過程:
1. 首先新建一個對象;
2. 然后將對象的原型指向 Person.prototype;
3. 然后 Person.apply(obj);
4. 返回這個對象;
注意這個時候,回顧下 apply 的實現步驟,會執行 obj.Person 方法,這個時候就會執行 if 語句里的內容,注意構造函數的 prototype 屬性指向了實例的原型,使用字面量方式直接覆蓋 Person.prototype,并不會更改實例的原型的值,person1 依然是指向了以前的原型,而不是 Person.prototype。而之前的原型是沒有 getName 方法的,所以就報錯了。
如果你就是想用字面量方式寫代碼,可以嘗試下這種:
function Person(name) {this.name = name;if (typeof this.getName != "function") {Person.prototype = {constructor: Person,getName: function () {console.log(this.name);}}return new Person(name)}
}var person1 = new Person('Jack1');
var person2 = new Person('Jack2');// 報錯 并沒有該方法
person1.getName();// 注釋掉上面的代碼,這句是可以執行的。
person2.getName();
5. 總結
在實際開發中,組合模式(構造函數+原型)是最常用的方式,它結合了構造函數模式和原型模式的優點,避免了各自的缺點,是創建自定義類型的首選方法。