防水堵漏公司做網(wǎng)站效果怎樣91
工廠模式
工廠模式 是用來創(chuàng)建對象的一種最常用的設(shè)計(jì)模式。工廠模式不暴露創(chuàng)建對象的具體邏輯,而是將邏輯封裝在一個函數(shù)中,那么這個函數(shù)就可以被視為一個工廠。工廠模式常見于大型項(xiàng)目,例如 jQuery 的 $
對象,我們創(chuàng)建選擇器對象之所以沒有 new selector
就是因?yàn)?$()
已經(jīng)是一個工廠方法,其他例子例如 React.createElement()
、Vue.component()
都是工廠模式的實(shí)現(xiàn)。
工廠模式根據(jù)抽象程度的不同可以分為三種:
- 簡單工廠:通過第三方的類完成松耦合的任務(wù)
- 復(fù)雜工廠:通過把實(shí)例化的任務(wù)交給子類來完成的,用以到達(dá)松耦合的目的
- 超級工廠:通過
eval()
來完成智能工廠
工廠的目的:在于判斷接口最終用哪個類實(shí)例化(故與接口密不可分)。
使用工廠最終達(dá)到的效果是:多態(tài),和類與類之間的松耦合。
應(yīng)用場景
ES5 實(shí)現(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 實(shí)現(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 時(shí),auth=1;權(quán)限為 user 時(shí),auth 為 2//使用者在外部創(chuàng)建對象時(shí),不需要知道各個權(quán)限對應(yīng)哪個字段, 不需要知道賦權(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
(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由 特定類型的所有實(shí)例共享的屬性和方法。如果按照字面意思來理解,那么 prototype
就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個對象實(shí)例的原型對象。使用原型對象的好處是可以讓所有對象實(shí)例共享它所包含的屬性和方法。換句話說,不必在構(gòu)造函數(shù)中定義對象實(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ù)不同,新對象的這些屬性和方法是由所有實(shí)例共享的。
理解原型對象
無論什么時(shí)候,只要創(chuàng)建一個新函數(shù),就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個 prototype
屬性,這個屬性指向函數(shù)的原型對象。在默認(rèn)情況下,所有原型對象都會自動獲得一個 constructor
(構(gòu)造函數(shù))屬性,這個屬性是一個指向 prototype
屬性所在函數(shù)的指針。
創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型對象默認(rèn)只會取得 constructor
屬性;至于其他方法,則都是從 Object 繼承而來的。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新的實(shí)例后,該實(shí)例的內(nèi)部將包含一個指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對象。ECMAScript 5 中管這個指針叫做 [[Prototype]]
。雖然在腳本中沒有標(biāo)準(zhǔn)的方式訪問 [[Prototype]]
,但 Firefox、Safari 和 Chrome 在每個對象上都支持一個屬性 __proto__
;而在其他實(shí)現(xiàn)中,這個屬性對腳本則是完全不可見的。不過,要明確的真正重要的一點(diǎn)就是,這個連接存在于實(shí)例與構(gòu)造函數(shù)的原型之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間。
原型最初只包含 constructor
屬性,而該屬性也是共享的,因此可以通過對象實(shí)例訪問。
雖然可以通過對象實(shí)例訪問保存在原型中的值,但卻不能通過對象實(shí)例重寫原型中的值。如果我們在實(shí)例中添加了一個屬性,而該屬性與實(shí)例原型中的一個屬性同名,那我們就在實(shí)例中創(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
兩個實(shí)例訪問 name
屬性的過程:
person1
==> 實(shí)例中讀取name
屬性 ==> 在實(shí)例中讀取name
屬性成功person2
==> 實(shí)例中讀取name
屬性 ==> 實(shí)例中無name
屬性 ==> 從原型鏈中讀取name
屬性 ==> 讀取成功
當(dāng)為對象實(shí)例添加一個屬性時(shí),這個屬性就會 屏蔽 原型對象中保存的同名屬性。換句話說,添加這個屬性只會阻止我們訪問原型中的那個屬性值,但不會修改那個屬性。即使這個屬性設(shè)置為 null
,也只會在實(shí)例中設(shè)置這個屬性,而不會恢復(fù)其指向原型的連接。不過,使用 delete
操作符則可以完全刪除實(shí)例屬性,從而讓我們能夠重新訪問原型中的屬性。
ECMAScript5 的
Object.getOwnPropertyDescriptor()
方法只能用于實(shí)例屬性,要取得原型屬性的描述符,必須直接在原型對象上調(diào)用Object.getOwnPropertyDescriptor()
方法。
原型與實(shí)例屬性檢測
有兩種方式使用 in 操作符:單獨(dú)使用和在 for-in
循環(huán)中使用。在單獨(dú)使用時(shí),in
操作符會在通過對象能夠訪問給定屬性時(shí)返回 true
,無論該屬性存在于實(shí)例中還是原型中。
同時(shí)使用 hasOwnProperty()
方法和 in
操作符,就可以確定該屬性到底是存在于對象中,還是存在于原型中。
由于 in
操作符只要通過對象能夠訪問到屬性就返回 true
,hasOwnProperty()
只在屬性存在于實(shí)例中時(shí)才返回 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ù),就會同時(shí)創(chuàng)建它的原型對象,這個對象自動獲得構(gòu)造函數(shù)。而這里的語法,這里相當(dāng)于重寫了實(shí)例的原型對象,相應(yīng)地原型對象中的構(gòu)造函數(shù) constructor
亦被覆蓋,不再指向 Person
函數(shù)。此時(shí),盡管 instanceof
操作符還能返回正確的結(jié)果,但通過 constructor
已經(jīng)無法確定對象的類型了。
當(dāng)然,我們可以手動為它設(shè)置回適當(dāng)?shù)闹?。但?#xff0c;以這種方式重設(shè) constructor
屬性回導(dǎo)致它的 [[Enumerable]]
特性被設(shè)置為 true
。默認(rèn)情況下,原生的 constructor
屬性是不可枚舉的。
function Person(){}Person.prototype = {constructor: Person,name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: function (){console.log(this.name);}
}
重設(shè)構(gòu)造函數(shù),只適用于 ECMAScript5 兼容的瀏覽器。
Object.defineProperty(Person, 'constructor', {enumerable: false,value: Person
})
原型的動態(tài)性
由于在原型中查找值的過程是一次搜索,因此我們對原型對象所做的任何修改都能夠立即從實(shí)例上反映出來,即使是先創(chuàng)建了實(shí)例后修改原型也照樣如此。
實(shí)例與原型之間的關(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)存在的對象實(shí)例之間的聯(lián)系,它們引用的仍然是最初的原型。
原型對象的原型
原型模式的重要性不僅體現(xiàn)在創(chuàng)建自定義類型方面,就連所有原生的引用類型,都是采用這種模式創(chuàng)建的。所有原生引用類型(Object、Array、String 等等)都在其構(gòu)造函數(shù)的原型上定義了方法。
通過原生對象的原型,不僅可以取得所有默認(rèn)方法的引用,而且也可以定義新方法。可以像修改自定義對象的原型一樣修改原生對象的原型,因此可以隨時(shí)添加方法。
盡管可以這樣做,但我們不推薦在產(chǎn)品化的程序中修改原生對象的原型。如果因某個實(shí)現(xiàn)中缺少某個方法,就在原生對象的原型中添加這個方法,那么當(dāng)在另一個支持該方法的實(shí)現(xiàn)中運(yùn)行代碼時(shí),就可能會導(dǎo)致命名沖突。而且,這樣做也可能會意外地重寫原生方法。
原型對象的問題
原型模式省略了為構(gòu)造函數(shù)傳遞初始參數(shù)的環(huán)節(jié),結(jié)果所有實(shí)例在默認(rèn)情況下都將取得相同的屬性值。
原型中的所有屬性是被很多實(shí)例共享的,這種共享對于函數(shù)非常合適。對于那些包含基本值的屬性倒也說得過去,畢竟,通過在實(shí)例上添加一個同名屬性,可以隱藏原型中的對應(yīng)屬性。然而,對于包含引用類型值的屬性來說,問題就比較突出了。
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