詳解 javascript對象創(chuàng)建模式
創(chuàng)建模式
在javascript中,主要有以下幾種創(chuàng)建模式:
工廠模式構(gòu)造函數(shù)模式原型模式組合模式動態(tài)原型模式寄生構(gòu)造函數(shù)模式穩(wěn)妥構(gòu)造模式
工廠模式
工廠模式是軟件工程領(lǐng)域一種廣為人知的設(shè)計模式。javascript實現(xiàn)方式:
function createPerson(name, obj, job) { var o = new Object(); o.name = name; o.obj = obj; o.job = job; o.sayName = function() { alert(this.name); } return o; } var person1 = createPerson('Nicholas', 29, 'software Enginner'); var person2 = createPerson('Greg', 27, 'Doctor');
工廠模式雖然解決了創(chuàng)建多個相似對象的問題,但卻沒有解決對象識別問題
構(gòu)造函數(shù)模式
function Person(name, age, job) { this.name = name; this.age = name; this.job = name; this.sayName = function () { alert(this.name); } } var person1 = new Person('Nicholas', 29, 'Software Engineer'); var person2 = new Person('Greg', 27, 'Doctor'); person1 instanceof Person; // true person1 instanceof Object; // true person2 instanceof Person; // true person2 instanceof Object; // true
new操作符實現(xiàn)原理請查看文章附錄
不同于工廠模式,構(gòu)造函數(shù)模式
沒有顯示創(chuàng)建對象
直接將屬性和方法賦值給了this對象
沒有return語句
解決了對象識別問題
但是構(gòu)造函數(shù)模式同樣存在問題,就是每個方法都要在每個實例上重新申明一遍。person1和person2都有一個名為 sayName() 的方法,但那兩個方法不是同一個Function實例。(在javascript中,函數(shù)實質(zhì)上也是對象,因此每定義一個函數(shù),也就是實例化一個對象。)
通過吧函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部可以解決這個問題:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert(this.name); } var person1 = new Person('Nicholas', 29, 'Software Engineer'); var person2 = new Person('Greg', 27, 'Doctor');
但這種方式又帶來了一個新的問題,我們在全局創(chuàng)建了一個全局函數(shù)。
需要注意一點,按照慣例,構(gòu)造函數(shù)始終應(yīng)該以一個大寫字母開頭,而非構(gòu)造函數(shù)應(yīng)該以一個小寫字母開頭。這主要用于區(qū)別構(gòu)造函數(shù)和非構(gòu)造函數(shù),因為構(gòu)造函數(shù)本身也是函數(shù)。
原型模式
我們創(chuàng)建的每個函數(shù)都有一個 prototype (原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途可以由特定類型的所有實例共享的屬性和方法。
函數(shù)原型對象請查看附錄
通過原型模式創(chuàng)建對象,我們不必在構(gòu)造函數(shù)中定義對象實例的信息,同時實例化多個對象,每個對象不會再申明一個新的函數(shù)。
可以看到, person1.sayName 和 person2.sayName 都指向了同一個函數(shù)。
但是原型模式的缺點也是顯而易見的。
首先原型模式省略了構(gòu)造函數(shù)模式傳遞參數(shù)這一環(huán)節(jié),結(jié)果導(dǎo)致所有實例的初始值在默認情況下都是相同的屬性值。
更重要的是,因為將屬性和方法都放置在原型對象上,實質(zhì)上原型上的屬性是 被所有實例所共享的 。對于包含基本值的屬性還表現(xiàn)正常,改變屬性值,只是在實例上添加一個同名屬性。但對于引用類型值的屬性來說,這可能是個災(zāi)難。
function Person() {} Person.prototype = { constructor: Person, name: 'Nicholas', age: 29, job: 'Software Engineer', friends: ['shelby', 'Court'], sayName: function() { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push('Van'); person1.friends; // ['shelby', 'Court', 'Van'] person2.friends; // ['shelby', 'Court', 'Van']
組合模式
創(chuàng)建自定義類型最常見的方式,就是組合使用構(gòu)造函數(shù)模式和原型模式。構(gòu)造模式用于定義實例屬性,而原型模式用于定義方法和共享屬性。
這樣,每個實例都會有自己的一份實例屬性副本,但同時又共享方法的引用。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ['Shelby', 'Court']; } Person.prototype.sayName = function() { alert(this.name); } var person1 = new Person('Nicholas', 29, 'Software Enginner'); var person2 = new Person('Greg', 27, 'Doctor'); person1.friends.push('Van'); person1.firends; // ['Shelby', 'Court', 'Van']; person2.firends; // ['Shelby', 'Court'] person1.firends === person2.firends; // false person1.sayName === person2.sayName; // true
動態(tài)原型模式
function Person(name, age, job) { this.name = name; this.age = age; this.job = job;if (typeof this.sayName != 'function') { Person.prototype.sayName = function() {alert(this.name); } } }
寄生構(gòu)造函數(shù)模式
寄生模式的基本概念就是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后再返回新創(chuàng)建的對象。
function Person(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job.o.sayName = function() { alert(this.name); } } var person1 = new Person('Nicholas', 29, 'Software Engineer'); person1.sayName(); // 'Nicholas'
看起來,除了使用new操作符之外,這個模式和工廠模式其實是一模一樣的。這個模式可以在特殊的情況下用來作為對象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們需要一個具有額外方法的特殊數(shù)組類型。由于不能直接修改Array構(gòu)造函數(shù),因此可以使用這個模式。
function SpecialArray() { var values = [];values.push.push(values, arguments); values.toPipedString = function() { return this.join('|'); }return values; } var colors = new SpecialArray('red', 'blue', 'green'); colors.toPipedString(); // 'red|blue|green'
該模式主要缺點:返回的對象和構(gòu)造函數(shù)或構(gòu)造函數(shù)的原型屬性間沒有關(guān)系,不·能依賴instanceof來確定對象類型。在其他模式能夠使用的情況下,盡量不要使用這種模式。
穩(wěn)妥構(gòu)造函數(shù)模式
function Person(name, age, job) { var o = new Object(); var name = name; var age = age; var job = jbo;o.sayName = function() { alert(name); } } var person1 = Person('Nicholas', 29, 'Software Enginner'); firend.sayName(); // 'Nicholas' 附錄 new 操作符
new操作符實際上會經(jīng)歷4個步驟:
創(chuàng)建一個空的簡單JavaScript對象(即**{}**); 鏈接該對象(設(shè)置該對象的constructor)到另一個對象 ; 將步驟1新創(chuàng)建的對象作為**this**的上下文 ; 如果該函數(shù)沒有返回對象,則返回**this**。function new(func) { var o = {}; o.__proto__ = func.prototype; var result = func.apply(o, arguments); return typeof result === 'object' ? object : o; }
函數(shù)原型對象
理解原型對象
無論什么時候,只要創(chuàng)建一個新函數(shù),就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性,這個屬性指向函數(shù)的原型對象。在默認情況下,所有原型對象都會自動獲得一個construtor(構(gòu)造函數(shù))屬性,這個屬性包含一個指向prototype屬性所在函數(shù)的指針。
在創(chuàng)建了一個自定義的構(gòu)造函數(shù)之后,其原型對象只會取得construtoe屬性,至于其他屬性,則都是從Object繼承而來。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新實例時,該實例的內(nèi)部將包含一個指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對象,這個指針叫[[Prototype]]。在多數(shù)瀏覽器中,每個對象都支持一個屬性__proto__來調(diào)用[[Prototype]]。
雖然所有實現(xiàn)都無法直接訪問[[Prototype]],但可以通過isPrototype方法來確定對象之間是否存在關(guān)系。
我們測試了person1和person2,都返回了true。因為他們內(nèi)部都有一個指向Person.prototype的指針。
Object.getPrototype()可以返回對象的原型對象。
每當(dāng)代碼讀取某個對象的屬性時,都會執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。搜索首先會從對象本身開始,如果在實例中找到了對應(yīng)的屬性,則返回該屬性的值。如果沒找到,則繼續(xù)搜索指針指向的原型對象。這也是為什么我們在person1和person2兩個實例中,并沒有定義sayName這個屬性,但仍能夠正常使用。我們在調(diào)用person1.sayName()是,會執(zhí)行兩次搜索。首先,解析器會問:“實例person1有sayName屬性嗎?”,答:“沒有”。然后他繼續(xù)搜索,再問:“person1的原型有sayName屬性嗎?”,答:“有”。于是,它就讀取保存在原型中的函數(shù)。
雖然我們能夠通過實例訪問原型的屬性,但卻不能重新原型的屬性。如果我們在實例上添加屬性名,而這個屬性名又與原型中的屬性名相同,即我們希望在實例中重寫屬性。
function Person() {} Person.prototype.name = ’Nicholas’; var person1 = new Person(); var person2 = new Person(); person1.name === person2.name; // true person1.name = ’Greg’; person1.name === person2.name; // false person1.name; // ’Greg’ person2.name; // ’Nicholas’ person1.__proto__.name; // ’Nicholas’
事實上,當(dāng)我們重寫原型屬性時,只是在實例上添加了一個新屬性。當(dāng)我們把實例上的屬性刪除后,又會暴露出原型屬性。
delete person1.name; person1.name; // ’Nicholas’
使用hasOwnProperty()函數(shù)能判斷屬性是否在實例上。
person1.hasOwnProperty(’name’); // false person1.name = ’Greg’; person1.hasOwnProperty(’name’); // true
以上就是詳解 javascript對象創(chuàng)建模式的詳細內(nèi)容,更多關(guān)于Java 創(chuàng)建模式的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
