- 工廠模式
缺點:雖然解決了創建多個相似對象的問題,但是沒能識別對象類型
例:
function createWorker(name, age, gender) {
var o = new Object();
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
var worker = createWorker('Mary', '21', 'female');
- 構造函數模式
例:
function Worker(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function(){
console.log(this.name);
}
}
var worker1 = new Worker('Mary', '21', 'female');
var worker2 = new Worker('Kimi', '20', 'male');
console.log(worker1 instanceof Worker); // true,新建了一個類型為Worker的一個對象
幾點tips:
- 構造函數的函數名首字母要大寫
- 實例對象時,要用new操作符
- 與工廠模式的對比
- 沒有顯示的新建對象
- 直接把屬性賦值給this
- 沒有return語句
- 構造函數的流程:
1.新建一個對象
2.把構造函數的作用域賦值給該對象(this)
3.執行構造函數中的代碼
4.返回新對象缺點:每個方法都要重新定義一遍
console.log(worker1.sayName == worker2.sayName);
// false,不同實例的同名函數其實是不相等的
- 原型模式
例:
function Worker(){
}
Worker.prototype.name = 'Mary';
Worker.prototype.age = '21';
Worker.prototype.gender = 'female';
Worker.prototype.sayName = function(){
console.log(this.name);
}
var worker1 = new Worker();
var worker2 = new Worker();
console.log(worker1.sayName == worker2.sayName);
// true 引用的是同一個函數,定義在原型對象中
原型對象:包含某個特定類型的所有屬性和方法
構造函數、原型對象和實例之間的關系:
- 初始化一個構造函數的時候,內部包含了一個 prototype 的屬性,,該屬性是一個指針,它指向了該構造函數對應的原型對象
- 原型對象(對于我們創建的構造函數,該對象中包含可以由所有實例共享的屬性和方法)自生成一個 constructor 屬性,該屬性也是一個指針,指向它的構造函數
- 在調用構造函數創建新的實例時,該實例的內部會自動包含一個[[Prototype]]指針屬性,該指針指便指向構造函數的原型對象。
關系圖:(我畫的有點亂糟糟)
image.png
幾點tips
- 如果在實例上定義了一個原型對象同名的屬性,則實例上的屬性高于原型對象的屬性;(當查找一個對象的屬性時,會現在實例上查找,如果有,則返回實例上該屬性值,查找停止;如果沒有,則繼續到原型對象里面去查找...)
接上面的例子:
worker1.name = 'Michael';
console.log(worker1.name); // 'Michael' 此時訪問的是實例中的name屬性
console.log(worker2.name);
// 'Mary' 由于在實例worker2中沒有該屬性,
因此繼續到原型對象中查找,找到返回Mary
判斷屬性是在原型中還是在實例中的方法:
- hasOwnPrototype:判斷某屬性是否存在于實例中
接上面的代碼:
console.log(worker1.hasOwnPrototype('name'));
// true, 在上面的代碼中,給worker1實例定義了一個name屬性
console.log(worker2.hasOwnPrototype('name'));
// false,對于worker2實例,name是在原型對象中定義的屬性
- in 操作符 :屬性如果存在于實例或者原型對象中,都會返回true
console.log('name' in worker1); // true
console.log('name' in worker2); // true
- 組合使用 in 操作符 和 hasOwnPrototype ,就可以很容易得到該屬性是否是只存在原型對象
獲得原型對象/實例的所有屬性
- for-in : 獲得對象 (包括原型對象)的所有可枚舉的屬性
worker1.job = 'teacher';
var props = [];
for(var prop in worker1){
props.push(prop);
}
console.log(props); // [name, age, gender, sayName, job]
- Object.keys() : 獲得實例的所有可枚舉的屬性
console.log(Object.keys(worker1)); // [name,job]
console.log(Object.keys(worker2)); // []
- Object.getOwnPropertyNames(): 獲得實例的所有屬性(不管是否可枚舉)
Object.defineProperty (worker1, 'hobby', { // es5 語法
value : 'music',
enumerable : false, // 將該屬性的可枚舉性改為false,默認值為true
})
console.log(Object.keys(worker1));
// [name, job] 不包含新增的不可枚舉的屬性 ‘hobby’
console.log(Object.getOwnPropertyNames(worker1));
// [name, job, hobby] 包含所有可枚舉不可枚舉的屬性
- 原型模式的改寫:
function Worker(){
}
var worker3 = new Worker();
Worker.prototype = {
constructor : Worker,
name = 'Mary';
age = '21',
gender = 'female',
sayName = function(){
console.log(this.name);
}
}
var worker4 = new Worker();
console.log(worker3.name);
// undefined, 此時實例worker3 指向最初原型對象,該對象未定義 name 屬性
console.log(worker4.name);
// 'Mary' 此時原型對象已經重寫,實例worker4指向該原型對象、
- 重寫原型對象時,要顯示地增加一個指向構造函數 Worker 的 constructor 的屬性;(對于支持es5的瀏覽器,可以用Object.defineProperty())
- 重寫原型對象以后,會切斷構造函數和最初原型對象之間的連接;
- 在重寫原型對象之前新建的實例,是無法獲取新原型對象內部定義的屬性的(而字面量的寫法,則不必在意是初始化原型對象前還是后新建實例的)
原型模式依然存在的問題:所有變量和方法都是共享的,而且初始時具有相同的值;改進=> 讓實例擁有自己的特定的屬性,把共用的方法和屬性定義在原型對象內部
構造函數+原型對象組合(運用最廣的一種模式)
function Worker(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.hobby = ['music', 'sing'];
}
Worker.prototype = {
constructor : Worker,
sayName : function(){
console.log(this.name);
},
}
var worker1 = new Worker('Mary', '21', 'female');
var worker2 = new Worker('Kimi', '20', 'male');
worker1.sayName(); // Mary
worker2.sayName(); // Kimi
worker1.hobby.push('swim');
console.log(worker1.hobby); // ['music', 'sing', 'swim'],
console.log(worker2.hobby); // ['music', 'sing']
動態原型模式 (把原型對象的內容也封裝在構造函數里)
function Worker(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.hobby = ['music', 'sing'];
if(typeof Worker.prototype.sayName != 'function'){
Worker.prototype.sayName = function(){
console.log(this.name);
}
}
}
幾個tips:
- 初次實例化時,就定義原型對象的屬性,僅執行一次
- if語句的條件只需要判斷原型對象內部的任意一個屬性或者方法就可以
- 這里不能使用原型對象的字面量寫法,因為如果已經創建了實例再重寫原型對象,則會切斷實例和最初原型對象的關聯
寄生式構造函數
function Worker(name, age, gender){
var o = new Object();
o.name = name;
o.age = age;
o.gender = gender;
o.sayName = function(){
console.log(this.name);
}
}
var worker1 = new Worker('Mary', '21', 'female');
幾點tips
- 這種方式構造對象和構造函數以及構造函數的原型對象之間沒有任何關系
(通過這個方式構造的對象和在普通對象沒什么區別)- 由于上一條,因此不能用 instanceof 區別對象類型
- 除了在新建實例的時候,用了new操作符,其他情況和工廠模式一致
- 在有些情況下可以為對象增加特殊屬性
(如給Array對象增加額外屬性和方法,就可以在構造函數內部,新建一個array類的對象,然后增加它的屬性,返回該對象)- 構造函數在不設置任何返回值的時候,默認返回新實例對象;如果設置了返回值的情況下,可以重寫調用構造函數時的返回值。
其他模式可用的情況下,盡量不選擇這種模式
穩妥構造函數
function Worker(name,age,gender){
var o = new Object();
o.sayName = function(){
console.log(name);
}
return o;
}
var worker =Worker('Mary', '21', 'female');
worker.sayName(); // Mary
幾點tips:
- 和寄生式構造函數一樣,該模式創建的對象不具有特殊類型的概念
- 構造函數內部不包含公共屬性,且公共方法不能使用this
- 創建對象時,不使用操作符 new 創建構造函數
- 除了在公共方法能訪問到name屬性的值,再無其他方式可以訪問
- 一般使用在安全執行環境中
總結: 以上就是創建對象的幾種模型,很大部分的概念是摘自《javascript高級程序設計》。最近又在重新看這個書,想著寫點東西,加強下自己的記憶和理解。平時感覺自己創建對象的時候太少了,之前看了某個大佬的直播,一開始他就像我平時寫代碼的方式一樣,想到啥就寫啥,但是最后人家重構了一下,把該封裝的都封裝成對象,可以說非常簡潔了。后來,我也重構了項目中的部分代碼,感覺很有用。其實也只是用到了非常簡單的工廠模式。平時寫代碼的時候,就顧著完成功能,而不注意代碼質量,這一點以后要加強吧。學以致用,這一周重構一下以前項目的部分頁面,練練手吧。接下來想著理一理繼承方面的知識點。不知道文中有沒有哪里沒弄對的,如有看到,歡迎指錯~