工廠模式
工廠模式 是用來創(chuàng)建對象的一種最常用的設計模式。工廠模式不暴露創(chuàng)建對象的具體邏輯,而是將邏輯封裝在一個函數(shù)中,那么這個函數(shù)就可以被視為一個工廠。工廠模式常見于大型項目,例如 jQuery 的 $
對象,我們創(chuàng)建選擇器對象之所以沒有 new selector
就是因為 $()
已經(jīng)是一個工廠方法,其他例子例如 React.createElement()
、Vue.component()
都是工廠模式的實現(xiàn)。
工廠模式根據(jù)抽象程度的不同可以分為三種:
- 簡單工廠:通過第三方的類完成松耦合的任務
- 復雜工廠:通過把實例化的任務交給子類來完成的,用以到達松耦合的目的
- 超級工廠:通過
eval()
來完成智能工廠
工廠的目的:在于判斷接口最終用哪個類實例化(故與接口密不可分)。
使用工廠最終達到的效果是:多態(tài),和類與類之間的松耦合。
應用場景
ES5 實現(xiàn)工廠模式
function createPerson(name, age, job) {
let person = new Object();
person.name = name;
person.age = age;
person.job = job;
person.sayNam = function () {
console.log(`I'm ${name}`);
};
return person;
}
const person1 = createPerson('Ben', 21, 'student');
const person2 = createPerson('Gray', 25, 'Doctor');
函數(shù) createPerson()
能夠根據(jù)接受的參數(shù)來構(gòu)建一個包含所有必要信息的 Person
對象??梢詿o數(shù)次調(diào)用這個函數(shù),而每次它都會返回一個包含三個屬性一個方法的對象。工廠模式雖然解決了創(chuàng)建多個相似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
ES6 實現(xiàn)工廠模式
class User {
constructor(name, auth) {
this.name = name;
this.auth = auth;
}
}
class UserFactory {
static createUser(name, auth) {
//工廠內(nèi)部封裝了創(chuàng)建對象的邏輯:
//權(quán)限為 admin 時,auth=1;權(quán)限為 user 時,auth 為 2
//使用者在外部創(chuàng)建對象時,不需要知道各個權(quán)限對應哪個字段, 不需要知道賦權(quán)的邏輯,只需要知道創(chuàng)建了一個管理員和用戶
if (auth === 'admin') return new User(name, 1);
if (auth === 'user') return new User(name, 2);
}
}
const admin = UserFactory.createUser('cxk', 'admin');
const user = UserFactory.createUser('xz', 'user');
原型模式
我們創(chuàng)建的每個函數(shù)都有一個 prototype
(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由 特定類型的所有實例共享的屬性和方法。如果按照字面意思來理解,那么 prototype
就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法。換句話說,不必在構(gòu)造函數(shù)中定義對象實例的信息,而是可以將這些信息直接添加到原型對象中。
function Person(){}
Person.prototype.name = 'Uzi';
Person.prototype.age = 22;
Person.prototype.job = 'E-Sports Player';
Person.prototype.sayName = function(){
console.log(this.name);
}
const uzi1 = new Person();
uzi1.sayName();
// 'Uzi'
const uzi2 = new Person();
uzi2.sayName();
// 'Uzi'
// 共用公用方法
console.log(person1.sayName == person2.sayName);
// true
與構(gòu)造函數(shù)不同,新對象的這些屬性和方法是由所有實例共享的。
理解原型對象
無論什么時候,只要創(chuàng)建一個新函數(shù),就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個 prototype
屬性,這個屬性指向函數(shù)的原型對象。在默認情況下,所有原型對象都會自動獲得一個 constructor
(構(gòu)造函數(shù))屬性,這個屬性是一個指向 prototype
屬性所在函數(shù)的指針。
創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型對象默認只會取得 constructor
屬性;至于其他方法,則都是從 Object 繼承而來的。當調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新的實例后,該實例的內(nèi)部將包含一個指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對象。ECMAScript 5 中管這個指針叫做 [[Prototype]]
。雖然在腳本中沒有標準的方式訪問 [[Prototype]]
,但 Firefox、Safari 和 Chrome 在每個對象上都支持一個屬性 __proto__
;而在其他實現(xiàn)中,這個屬性對腳本則是完全不可見的。不過,要明確的真正重要的一點就是,這個連接存在于實例與構(gòu)造函數(shù)的原型之間,而不是存在于實例與構(gòu)造函數(shù)之間。
原型最初只包含 constructor
屬性,而該屬性也是共享的,因此可以通過對象實例訪問。
雖然可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。如果我們在實例中添加了一個屬性,而該屬性與實例原型中的一個屬性同名,那我們就在實例中創(chuàng)建該屬性,該屬性將會屏蔽原型中的屬性。
function Person(){}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
console.log(this.name);
};
const person1 = new Person();
const person2 = new Person();
person1.name = 'Greg';
console.log(person1.name);
// 'Greg' // from instance
console.log(person2.name);
// 'Nicholas' // from prototype
兩個實例訪問 name
屬性的過程:
-
person1
==> 實例中讀取name
屬性 ==> 在實例中讀取name
屬性成功 -
person2
==> 實例中讀取name
屬性 ==> 實例中無name
屬性 ==> 從原型鏈中讀取name
屬性 ==> 讀取成功
當為對象實例添加一個屬性時,這個屬性就會 屏蔽 原型對象中保存的同名屬性。換句話說,添加這個屬性只會阻止我們訪問原型中的那個屬性值,但不會修改那個屬性。即使這個屬性設置為 null
,也只會在實例中設置這個屬性,而不會恢復其指向原型的連接。不過,使用 delete
操作符則可以完全刪除實例屬性,從而讓我們能夠重新訪問原型中的屬性。
ECMAScript5 的
Object.getOwnPropertyDescriptor()
方法只能用于實例屬性,要取得原型屬性的描述符,必須直接在原型對象上調(diào)用Object.getOwnPropertyDescriptor()
方法。
原型與實例屬性檢測
有兩種方式使用 in 操作符:單獨使用和在 for-in
循環(huán)中使用。在單獨使用時,in
操作符會在通過對象能夠訪問給定屬性時返回 true
,無論該屬性存在于實例中還是原型中。
同時使用 hasOwnProperty()
方法和 in
操作符,就可以確定該屬性到底是存在于對象中,還是存在于原型中。
由于 in
操作符只要通過對象能夠訪問到屬性就返回 true
,hasOwnProperty()
只在屬性存在于實例中時才返回 true
,因此只要 in
操作符返回 true
而 hasOwnProperty()
返回 false
,就可以確定屬性是原型中的屬性。
更簡單的原型語法
前面的例子中每添加一個屬性和方法就要輸入一遍 Person.prototype
,為減少不必要的輸入,也為了從視覺上更好地封裝原型的功能,更常見的做法是用一個包含所有屬性和方法的對象字面量來重寫整個原型對象。
function Person(){}
Person.prototype = {
name: 'Nicholas',
age: 29,
job: 'Software Engineer',
sayName: function (){
console.log(this.name);
}
}
前面介紹過,沒創(chuàng)建一個函數(shù),就會同時創(chuàng)建它的原型對象,這個對象自動獲得構(gòu)造函數(shù)。而這里的語法,這里相當于重寫了實例的原型對象,相應地原型對象中的構(gòu)造函數(shù) constructor
亦被覆蓋,不再指向 Person
函數(shù)。此時,盡管 instanceof
操作符還能返回正確的結(jié)果,但通過 constructor
已經(jīng)無法確定對象的類型了。
當然,我們可以手動為它設置回適當?shù)闹?。但是,以這種方式重設 constructor
屬性回導致它的 [[Enumerable]]
特性被設置為 true
。默認情況下,原生的 constructor
屬性是不可枚舉的。
function Person(){}
Person.prototype = {
constructor: Person,
name: 'Nicholas',
age: 29,
job: 'Software Engineer',
sayName: function (){
console.log(this.name);
}
}
重設構(gòu)造函數(shù),只適用于 ECMAScript5 兼容的瀏覽器。
Object.defineProperty(Person, 'constructor', {
enumerable: false,
value: Person
})
原型的動態(tài)性
由于在原型中查找值的過程是一次搜索,因此我們對原型對象所做的任何修改都能夠立即從實例上反映出來,即使是先創(chuàng)建了實例后修改原型也照樣如此。
實例與原型之間的關(guān)系是松散的,
function Person(){}
const friend = new Person();
Person.prototype = {
constructor: Person,
name: 'Nicholas',
age: 29,
job: 'Software Engineer',
sayName: function (){
console.log(this.name);
}
};
friend.sayName();
// error
重寫原型對象切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對象實例之間的聯(lián)系,它們引用的仍然是最初的原型。
原型對象的原型
原型模式的重要性不僅體現(xiàn)在創(chuàng)建自定義類型方面,就連所有原生的引用類型,都是采用這種模式創(chuàng)建的。所有原生引用類型(Object、Array、String 等等)都在其構(gòu)造函數(shù)的原型上定義了方法。
通過原生對象的原型,不僅可以取得所有默認方法的引用,而且也可以定義新方法??梢韵裥薷淖远x對象的原型一樣修改原生對象的原型,因此可以隨時添加方法。
盡管可以這樣做,但我們不推薦在產(chǎn)品化的程序中修改原生對象的原型。如果因某個實現(xiàn)中缺少某個方法,就在原生對象的原型中添加這個方法,那么當在另一個支持該方法的實現(xiàn)中運行代碼時,就可能會導致命名沖突。而且,這樣做也可能會意外地重寫原生方法。
原型對象的問題
原型模式省略了為構(gòu)造函數(shù)傳遞初始參數(shù)的環(huán)節(jié),結(jié)果所有實例在默認情況下都將取得相同的屬性值。文章來源:http://www.zghlxwxcb.cn/news/detail-840628.html
原型中的所有屬性是被很多實例共享的,這種共享對于函數(shù)非常合適。對于那些包含基本值的屬性倒也說得過去,畢竟,通過在實例上添加一個同名屬性,可以隱藏原型中的對應屬性。然而,對于包含引用類型值的屬性來說,問題就比較突出了。文章來源地址http://www.zghlxwxcb.cn/news/detail-840628.html
function Person(){}
Person.prototype = {
name: 'Nicholas',
age: 29,
job: 'Software Engineer',
friends: ['Shelby', 'Court'],
sayName: function (){
console.log(this.name);
}
}
const person1 = new Person();
const person2 = new Person();
person1.friends.push('Van');
console.log(person1.friends);
// 'Shelby,Court,Van'
console.log(person2.friends);
// 'Shelby,COurt,Van'
console.log(person1.friends == person2.friends);
// true
到了這里,關(guān)于【編程向?qū)А縅avaScript-創(chuàng)建對象一期講解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!