通過Object構(gòu)造函數(shù)或?qū)ο笞置媪縿?chuàng)建對象時,使用同一個接口創(chuàng)建很多對象時,會產(chǎn)生大量的重復(fù)代碼。為了簡化,引入了工廠模式。
工廠模式
function createPerson(name, age, job) { var obj = new Object(); obj.name = name; obj.age = age; obj.job = job; obj.sayHello(){ alert(this.name); }; return obj;}var p1 = createPerson("xxyh", 19, "programmer");var p2 = createPerson("zhangsan", 18, "student");
這種創(chuàng)建對象的方式大大簡化了代碼,然而也存在不足,那就是無法確定對象的類型。為了解決這個問題,出現(xiàn)下面這種模式。
構(gòu)造函數(shù)模式
創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對象類型的屬性和方法。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); };}var p1 = new Person("xxyh", 19, "programmer");var p2 = new Person("Jack", 18, "student");
上例中,Person()取代了createPerson(),除此之外,還有幾點不同:
•沒有顯示地創(chuàng)建對象;
•直接將屬性和方法賦值給了this對象
•沒有return語句
創(chuàng)建Person對象,必須使用new操作符。分為4個步驟:
•創(chuàng)建一個新對象
•將構(gòu)造函數(shù)的作用域賦給新對象
•執(zhí)行構(gòu)造函數(shù)中的代碼
•返回新對象
p1和p2分別保存著Person的一個實例。
alert(p1.constructor == Person); // truealert(p2.constructor == Person); // true
檢測類型時最好使用instanceof:
alert(p1 instanceof Object); // truealert(p1 instanceof Person); // truealert(p2 instanceof Object); // truealert(p2 instanceof Person); // true
p1和p2都是Object的實例,因為所有對象均繼承自O(shè)bject。
2.1將構(gòu)造函數(shù)當(dāng)作函數(shù)
// 當(dāng)作構(gòu)造函數(shù)使用var person = new Person("xxyh", 19, "programmer");person.sayName(); // "xxyh"http:// 當(dāng)作普通函數(shù)Person("zhangsan", 18, "student"); // 添加到windowwindow.sayName(); // "zhangsan"http:// 在另一個對象的作用域中調(diào)用var obj = new Object();Person.call(obj, "Jack", 29, "manager");obj.sayName(); // "Jack",obj擁有了所有屬性和方法
2.2構(gòu)造函數(shù)的問題
使用構(gòu)造函數(shù)的問題,就是每個方法都要在每個實例上重新創(chuàng)建一遍。p1和p2都有一個sayName()方法,但是他們不是一個Function的實例。在JavaScript中,函數(shù)時對象,因此每定義一個函數(shù),就實例化了一個對象。
構(gòu)造函數(shù)也可以這樣定義:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)");}
因此,不同實例上的同名函數(shù)時不相等的:
alert(p1.sayName == p2.sayName); // false
然而,創(chuàng)建兩個同樣功能的Function是多余的,根本不需要在執(zhí)行代碼前就把函數(shù)綁定到特定對象上面。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName;}function sayName() { alert(this.name);}var p1 = new Person("xxyh", 19, "programmer");var p2 = new Person("Jack", 18, "student");
上面將sayName()的定義移到構(gòu)造函數(shù)外部,然后在構(gòu)造函數(shù)內(nèi)部將屬性sayName設(shè)置為全局的sayName函數(shù)。這樣,sayName包含了指向函數(shù)的指針,p1和p2共享了全局作用域中定義的同一個sayName()函數(shù)。
但是,這樣做又出現(xiàn)了新問題:在全局作用域中定義的函數(shù)只能被某個對象調(diào)用。而且如果對象定義了很多方法,那么引用類型就失去了封裝性。
原型鏈模式
每個函數(shù)都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象。這個對象的用途是:包含可以由特定類型的所有實例共享的屬性和方法。prototype是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法。這就是說不必在構(gòu)造函數(shù)中定義對象實例的信息,而是將這些信息添加到原型對象中。
function Person() {}Person.prototype.name = "xxyh";Person.prototype.age = 19;Person.prototype.job = "programmer";Person.prototype.sayName = function () { alert(this.name);};var person1 = new Person();person1.sayName(); // "xxyh"var person2 = new Person();person2.sayName(); // "xxyh"alert(person1.sayName == person2.sayName); // true
3.1理解原型對象
只要創(chuàng)建一個新函數(shù),就會為該函數(shù)創(chuàng)建一個prototype屬性,這個屬性指向函數(shù)的原型對象。在默認(rèn)情況下,所有原型對象都會自動獲得一個constructor屬性。這個屬性包含一個指向prototype屬性所在函數(shù)的指針。Person.prototype.constructor指向Person。
當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個實例,實例的內(nèi)部將包含指向構(gòu)造函數(shù)的原型對象的指針(內(nèi)部屬性),稱為[[Prototype]]。在Firefox、Safari和Chrome通過_proto_訪問。這個連接存在于實例與構(gòu)造函數(shù)的原型對象之間,而不是存在于實例與構(gòu)造函數(shù)之間。
下圖展示了各個對象之間的關(guān)系:
Person.prototype指向了原型對象,而Person.prototype.constructor又指回了Person。原型中除了constructor屬性,還有其他添加的屬性。Person實例中都包含一個內(nèi)部屬性,該屬性僅僅指向了Person.prototype,它們和構(gòu)造函數(shù)沒有直接關(guān)系。
雖然無法訪問[[Prototype]],但是可以通過isPrototypeOf()方法來確定對象之間是否存在這種關(guān)系。
alert(Person.prototype.isPrototypeOf(person1)); // truealert(Person.prototype.isPrototypeOf(person2)); // true
在讀取某個對象的屬性時,都會執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。搜索首先從對象實例本身開始。搜索首先從對象實例本身出發(fā)開始,如果在實例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,則繼續(xù)搜索指針指向的原型對象,在原型對象中查找給定名字的屬性。如果在原型對象中找到了這個屬性,則返回屬性的值。
可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。如果在實例中添加一個與實例原型中的一個屬性同名的屬性,該屬性將會屏蔽原型中的屬性。
function Person() {}Person.prototype.name = "xxyh";Person.prototype.age = "20";Person.prototype.job = "programmer";Person.prototype.sayName = function () { alert(this.name);};var person1 = new Person();var person2 = new Person();person1.name = "oooo";alert(person1.name); // "oooo"alert(person2.name); // "xxyh"
上例中,person1中的name屬性屏蔽了原型中的name屬性。
當(dāng)對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。這也就是說,這個屬性的存在會阻止對原型中那個屬性的訪問。使用delete可以完成刪除實例屬性。
function Person() {}Person.prototype.name = "xxyh";Person.prototype.age = "20";Person.prototype.job = "programmer";Person.prototype.sayName = function () { alert(this.name);};var person1 = new Person();var person2 = new Person();person1.name = "oooo";alert(person1.name); // "oooo"alert(person2.name); // "xxyh"delete person1.name;alert(person1.name); // "xxyh"
hasOwnProperty()可以檢測一個屬性是存在于實例中,還是存在于原型中。
function Person() {}Person.prototype.name = "xxyh";Person.prototype.age = "20";Person.prototype.job = "programmer";Person.prototype.sayName = function () { alert(this.name);};var person1 = new Person();var person2 = new Person();alert(person1.hasOwnProperty("name")); // falseperson1.name = "oooo";alert(person1.hasOwnProperty("name")); // true
下圖展示了不同情況的實現(xiàn)與原型的關(guān)系:
3.2原型與in操作符
使用in操作符的方式:單獨使用、在for-in循環(huán)中使用。在單獨使用時,in操作符會在通過對象能夠訪問給定屬性時返回true,無論該屬性存在于實例中還是原型中。
function Person() {}Person.prototype.name = "xxyh";Person.prototype.age = "20";Person.prototype.job = "programmer";Person.prototype.sayName = function () { alert(this.name);};var person1 = new Person();alert("name" in person1); // trueperson1.name = "oooo";alert("name" in person1); // true
結(jié)合前面的hasOwnProperty()特點,可以確定某個屬性是原型中的屬性還是實例中的屬性。如果in操作符返回true而hasOwnProperty返回false,則屬性是原型中的屬性。
function hasPrototypeProperty(object, name) { return !object.hasOwnProperty(name)&& (name in object);}
接下來,看看hasPrototypeProperty()的用法:
function Person() {}Person.prototype.name = "xxyh";Person.prototype.age = "20";Person.prototype.job = "programmer";Person.prototype.sayName = function () { alert(this.name);};var person = new Person();alert(hasPrototypeProperty(person, "name")); // trueperson.name = "oooo";alert(hasPrototypeProperty(person, "name")); // false
在使用for-in循環(huán)時返回的是所有能夠通過對象訪問的、可枚舉的屬性,包括實例中的屬性和原型中的屬性。屏蔽了原型中不可枚舉數(shù)據(jù)(即[[Enumerable]]標(biāo)記為false的屬性)的實例屬性也會在for-in中返回,因為根據(jù)規(guī)定,開發(fā)人員定義的屬性都是可枚舉的。
要取得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法。
function Person() {}Person.prototype.name = "xxyh";Person.prototype.age = "20";Person.prototype.job = "programmer";Person.prototype.sayName = function () { alert(this.name);};var keys = Object.keys(Person.prototype);alert(keys); // name, age, job, sayNamevar p1 = new Person();p1.name = "oooo";p1.age = 15;var p1_keys = Object.keys(p1);alert(p1_keys); // name, age
如果需要得到所有實例屬性,可以使用Object.getOwnPropertyNames()方法
var keys = Object.getOwnPropertyNames(Person.prototype);alert(keys); // "constructor,name,age,job,sayName"
3.3更簡單的原型語法
為了精簡輸入,用一個包含所有屬性和方法的對象字面量來重寫整合原型對象。
function Person() { }Person.prototype = { name : "xxyh", age : 18, job : "programmer", sayName : function () { alert(this.name); }};
上面將Person.prototype設(shè)置為等于一個以對象字面量形式創(chuàng)建的新對象。結(jié)果相同,但是constructor屬性不在指向Person了。
通過instanceof能返回正確結(jié)果,但是constructor無法確定對象的類型:
var boy = new Person();alert(boy instanceof Object); // truealert(boy instanceof Person); // truealert(boy.constructor == Person); // falsealert(boy.constructor == Object); // true
可以通過下面的方式設(shè)置constructor的值:
function Person() {}Person.prototype = { constructor : Person, name : "xxyh", age : 18, job : "programmer", sayName : function () { alert(this.name); }};
3.4原型鏈的動態(tài)性
由于在原型中查找值的過程是一次搜索,因此對原型對象所做的任何修改都會反映到實例上。但是如果重寫整個原型對象,結(jié)果就不同了。調(diào)用構(gòu)造函數(shù)時會為實例添加一個指向最初原型的[[prototype]]指針,而把原型修改為另一個對象就等于切斷了構(gòu)造函數(shù)與最初原型的聯(lián)系。實例中的指針僅指向原型,而不指向構(gòu)造函數(shù)。
function Person() {}var boy = new Person();Person.prototype = { constructor : Person, name : "xxyh", age : 29, job : "programmer", sayName : function () { alert(this.name); }};boy.sayName(); // 錯誤
具體過程如下:
從上面可以看出,重寫原型對象切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對象實例之間的聯(lián)系;它們引用的是最初的原型。
3.5原生對象的原型
所有原生引用類型都是在構(gòu)造函數(shù)的原型上定義了方法。通過原生對象的原型,不僅可以取得默認(rèn)方法,而且可以定義新方法。
String.prototype.startsWith = function (text) { return this.indexOf(text) == 0;};var msg = "good morning";alert(msg.startsWith("good")); // true
3.6原型對象的問題
原型模式存在兩個問題:
•在默認(rèn)情況下都取得相同的屬性值。
•原型中的所有屬性是實例共享的
下面看一個例子:
function Person() {}Person.prototype = { constructor: Person, name: "xxyh", age : 18, job : "programmer", friends:["張三", "李四"], sayName: function () { alert(this.name); }};var p1 = new Person();var p2 = new Person();p1.friends.push("王五");alert(p1.friends); // 張三,李四,王五alert(p2.friends); // 張三,李四,王五alert(p1.friends == p2.friends); // true
上面通過p1.friends添加了一項,由于friends數(shù)組存在于Person.prototype中,所以在p2.friends也反映出來了。可是,實例一般都是要有屬于自己的全部屬性的。
組合使用構(gòu)造函數(shù)模式和原型模式
構(gòu)造函數(shù)模式用于定義實例屬性,原型模式用于定義方法和共享的屬性。這樣,每個實例都會有自己的一份實例屬性的副本,但是同時又共享著對方法的引用。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["張三", "李四"];}Person.prototype = { constructor: Person, sayName: function () { alert(this.name); }}var p1 = new Person("蕭蕭弈寒", 18, "programmer");var p2 = new Person("魁拔", 10, "捉妖");p1.friends.push("王五");alert(p1.friends); // 張三,李四,王五alert(p2.friends); // 張三,李四alert(p1.friends == p2.friends); // falsealert(p1.sayName == p2.sayName); // true
上例中,實例屬性都是在構(gòu)造函數(shù)中定義的,共享屬性constructor和方法sayName()則是在原型中定義的。p1.friends的修改并不會影響到p2.friends的結(jié)果。
動態(tài)原型模式
動態(tài)原型模式把所有信息都封裝在了構(gòu)造函數(shù)中,而通過在構(gòu)造函數(shù)中初始化原型,又保持了同時使用構(gòu)造函數(shù)和原型的優(yōu)點。這就是說,可以通過檢查某個應(yīng)該存在的方法是否有效,來決定是否需要初始化原型。
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); } }}
這里只在sayName()方法不存在時,才會將它添加到原型中,只會在初次調(diào)用構(gòu)造函數(shù)時執(zhí)行。
寄生構(gòu)造函數(shù)模式
這種模式的思想是創(chuàng)建一個函數(shù),該函數(shù)的作用是封裝創(chuàng)建對象的代碼,然后再返回新創(chuàng)建的對象。
function Person(name, age) { var obj = new Object(); obj.name = name; obj.age = age; obj.sayName = function () { alert(this.name); } return obj;}var boy = new Person("xxyh", 19, "programmer");boy.sayName();
需要說明:首先,返回的對象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬性之間沒有關(guān)系;構(gòu)造函數(shù)返回的對象與在構(gòu)造函數(shù)外部創(chuàng)建的對象沒有不同。不能依賴instanceof操作符來確定對象類型。
穩(wěn)妥構(gòu)造函數(shù)模式
穩(wěn)妥對象指的是沒有公共屬性,而且其方法也不引用this的對象。穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)類似的模式,但是有兩點不同:
•新創(chuàng)建對象的實例方法不引用this;
•不使用new操作符調(diào)用構(gòu)造函數(shù)
重寫Person構(gòu)造函數(shù)如下:
function Person(name, age, job) { var obj = new Object(); obj.sayName = function () { alert(name); }; return obj;}
function Person(name, age, job) { var obj = new Object(); obj.sayName = function () { alert(name); }; return obj;}
以上這篇淺談JavaScript對象的創(chuàng)建方式就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持武林網(wǎng)。
新聞熱點
疑難解答