Object構造函數 創建自定義對象最簡單的方式就是創建一個 Object 的實例,然後再為它添加屬性和方法: 缺點 代碼冗餘,會產生大量重覆代碼 無法識別對象(無法知道對象的類型) 對象字面量 對象字面量相比較於 Object 構造函數,代碼會比較直觀一些: 缺點 代碼冗餘,會產生大量重覆代碼 無 ...
Object構造函數
創建自定義對象最簡單的方式就是創建一個 Object 的實例,然後再為它添加屬性和方法:
// 創建對象
var person = new Object();
// 定義屬性
person.name = 'laixiangran';
person.age = 28;
person.job = 'Front End Software Engineer';
// 定義方法
person.sayName = function() {
console.log(this.name);
};
person.sayName(); // 'laixiangran'
缺點
- 代碼冗餘,會產生大量重覆代碼
- 無法識別對象(無法知道對象的類型)
對象字面量
對象字面量相比較於 Object 構造函數,代碼會比較直觀一些:
var person = {
name: 'laixiangran',
age: 28,
job: 'Front End Software Engineer',
sayName: function() {
console.log(this.name);
}
};
person.sayName(); // 'laixiangran'
缺點
- 代碼冗餘,會產生大量重覆代碼
- 無法識別對象(無法知道對象的類型)
工廠模式
Object 構造函數或對象字面量這兩種方法的缺點就是:使用同一個介面創建很多對象時,會產生大量的重覆代碼。為解決這個問題,我們將利用工廠模式來創建一個函數,這個函數將創建具體對象的過程進行封裝:
function creatPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
// 使用函數creatPerson創建對象
var person1 = creatPerson('laixiangran', 28, 'Front End Software Engineer');
person1.sayName(); // 'laixiangran'
var person2 = creatPerson('lai', 29, 'Back End Software Engineer');
person2.sayName(); // 'laixiangran'
通過creatPerson()能夠根據參數無數次地創建不同的對象,這樣就達到復用的目的,而且創建對象的細節是透明的。
工廠模式雖然解決了創建多個相似對象的問題,但是沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
缺點
- 無法識別對象(無法知道對象的類型)
構造函數模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
var person1 = new Person('laixiangran', 28, 'Front End Software Engineer');
person1.sayName(); // 'laixiangran'
var person2 = new Person('lai', 29, 'Back End Software Engineer');
person2.sayName(); // 'laixiangran'
在這個例子中,Person() 函數取代了 creatPerson() 函數,不同之處在於:
- 沒有顯式地創建對象
- 直接將屬性和方法賦給了 this 對象
- 沒有return語句
因此,要創建 Person 的新實例,則必須使用 new
操作符。以這種方式調用構造函數實際上會經歷以下4個步驟:
- 創建一個新對象
- 將構造函數的作用域賦給新對象(因此 this 就指向了這個新對象)
- 執行構造函數中的代碼(為這個新對象添加屬性和方法)
- 返回新對象
所以,當我們使用 new
操作符來調用構造函數時,實際就是隱式地完成 creatPerson() 函數要完成的那些工作。
當然,這種模式 解決了對象識別的問題 。在前面的例子中,person1 和 person2 分別保存著 Person 的一個不同的實例。這兩個對象都有一個 constructor(構造函數)屬性,該屬性指向 Person,這樣就達到對象識別了(能知道對象的類型)。還有,檢測對象類型,我們一般使用 instanceof 操作符。最後,我們知道所有的對象都是繼承自 Object 的,因此下麵的代碼都返回 true:
console.log(person1.constructor === Person); // true
console.log(instanceof person1 Person); // true
console.log(instanceof person1 Object); // true
console.log(person2.constructor === Person); // true
console.log(instanceof person2 Person); // true
console.log(instanceof person2 Object); // true
缺點
- 每個方法都要在每個實例上重新創建一遍
- 如果不想重新創建一遍,函數只能先在全局作用域中定義,但是這樣對於自定義對象來說就沒有封裝性可言了
原型模式
我們創建的每個函數都有一個 prototype(原型)
屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共用的屬性和方法。如果按照字面量意思來理解,那麼 prototype
就是通過 調用構造函數而創建
的那個 對象實例
的 原型對象
。
不清楚 原型對象
可以先自行瞭解下,本文不展開介紹 原型對象
,後面會寫文章單獨介紹。
使用原型對象的好處是可以讓所有的對象實例共用它所包含的屬性和方法。換句話說,不必在構造函數中定義對象實例的信息,而是可以將這些信息直接添加到原型對象中:
function Person() {
}
Person.prototype.name = 'laixiangran';
Person.prototype.age = 28;
Person.prototype.job = 'Front End Software Engineer';
Person.prototype.friends = ['xu', 'song'];
Person.prototype.sayName = function() {
console.log(this.name);
};
var person1 = new Person();
person1.sayName(); // 'laixiangran'
person1.friends.push('chen');
console.log(person1.friends); // 'xu', 'song', 'chen'
var person2 = new Person();
person2.sayName(); // 'laixiangran'
console.log(person2.friends); // 'xu', 'song', 'chen'
console.log(person1.sayName === person2.sayName); // true
console.log(person1.friends === person2.friends); // true
缺點
- 由於是所有實例共用屬性和方法,如果修改引用類型值的屬性(如對象、數組),那麼就會影響所有的對象實例(往往我們都希望每個實例都有自己的屬性)
組合使用構造函數模式和原型模式
創建自定義類型的最常見方式,就是組合使用構造函數模式與原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共用屬性。這樣,每個實例都會有自己的一份實例屬性的副本,但同時又共用著對方法的引用,最大限度地節省了記憶體。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ['xu', 'song'];
}
Person.prototype.sayName = function() {
console.log(this.name);
};
var person1 = new Person('laixiangran', 28, 'Front End Software Engineer');
var person2 = new Person('lai', 29, 'Back End Software Engineer');
person1.friends.push('chen');
console.log(person1.friends); // 'xu', 'song', 'chen'
console.log(person2.friends); // 'xu', 'song'
console.log(person1.sayName === person2.sayName); // true
console.log(person1.friends === person2.friends); // false
這種混成模式集構造函數模式和原型模式這兩種模式之長。
缺點
- 構造函數和原型分別獨立,代碼封裝型不強
動態原型模式
這種模式是對 組合使用構造函數模式和原型模式
方法的改進,它將所有信息都封裝在了構造函數中,而通過在構造函數中初始化原型(可僅在必要的情況下),又保持了 組合使用構造函數模式和原型模式
方法的優點。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
// 檢查是否存在sayName方法來決定是否初始化原型
if (typeof this.sayName !== 'function') {
Person.prototype.sayName = function() {
console.log(this.name);
};
}
}
var person1 = new Person('laixiangran', 28, 'Front End Software Engineer');
person1.sayName(); // 'laixiangran'
其中,if 語句檢查可以是初始化之後應該存在的任何屬性和方法,不必對每個方法和屬性都判斷,只需要判斷其中一個即可。
寄生構造函數模式
function Person(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
var person1 = new Person('laixiangran', 28, 'Front End Software Engineer');
person1.sayName(); // 'laixiangran'
var person2 = new Person('lai', 29, 'Back End Software Engineer');
person2.sayName(); // 'laixiangran'
除了使用 new 操作符並把使用的包裝函數叫做構造函數之外,這種模式其實和 工廠模式
是一模一樣的。構造函數中的 return 語句重寫了通過 new 操作符調用構造函數預設返回的新對象實例。
這種模式可以在特殊情況下用來為對象創建構造函數。假設我們想創建一個具有額外方法的特殊數組,但又不能直接修改 Array 構造函數:
function SpecialArray() {
// 創建數組
var values = new Array();
// 添加值
values.push.apply(values, arguments);
// 添加方法
values.toPipedString = function() {
return this.join('|');
};
// 返回數組
return values;
}
var colors = new SpecialArray('red', 'blue', 'green');
console.log(colors.toPipedString()); // 'red|blue|green'
缺點
- 由於該模式返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係,因此,這種模式並不能通過 instanceof 操作符來確定對象類型。
穩妥構造函數模式
所謂穩妥對象,指的是沒有公共屬性,而且其方法也不引用 this 的對象。穩妥對象最適合在一些安全環境中(這些環境中會禁止使用 this 和 new),或者在防止數據被其他應用程式(如 Mashup 程式)改動時使用。
與 寄生構造函數模式 有兩點不同:
- 新創建對象的實例方法不引用 this
- 不使用 new 操作符調用構造函數
function Person(name, age, job) {
// 創建要返回的對象
var o = new Object();
// 可以在這裡定義私有變數和方法
// 添加方法
o.sayName = function() {
console.log(name);
};
// 返回對象
return o;
}
var person1 = Person('laixiangran', 28, 'Front End Software Engineer');
person1.sayName(); // 'laixiangran'
變數 person1 中保存的是一個穩妥對象,除了調用 sayName() 方法外,沒有別的方式可以訪問其數據成員。
缺點
- 與 寄生構造函數模式 一樣,由於該模式返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係,因此,這種模式並不能通過 instanceof 操作符來確定對象類型。
參考資料:《JavaScript高級程式設計(第3版)》第6.2節 創建對象