理解對象和創(chuàng)建對象
1.理解對象
- 創(chuàng)建一個Object的實例,然為其添加屬性和方法(早期創(chuàng)建對象的模式)
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
alert(this.name);
}
- 用對象字面量語法重寫上述過程:
var person = {
name: "Nicholas";
age: 29;
job: "Software Engineer";
sayName: function(){
alert(this.name);
}
}
1.1 屬性類型
ECMAScript中有兩種 屬性(property) :數(shù)據(jù)屬性和訪問器屬性憎兽,用 特性(attribute) 來描述屬性的各種特征冷离。
數(shù)據(jù)屬性:
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置,在這個位置可以讀取和寫入值纯命。
數(shù)據(jù)屬性有四個描述其行為的特性:
- [[Enumerable]]:表示能否通過 for-in循環(huán)返回屬性西剥,默認值為true。
- 要修改屬性默認的特性亿汞,必須使用ECMAScript的Object.defineProperty()方法
訪問器屬性
訪問器屬性不包含數(shù)據(jù)值瞭空,它們包含一對getter和setter函數(shù)(不是必須的)。在讀取訪問器屬性時疗我,會調(diào)用getter函數(shù)匙铡,在寫入訪問器屬性時,會調(diào)用setter函數(shù)并傳入新值碍粥。訪問器屬性有以下四個特性:
- [[Enumerable]]:表示能否通過 for-in循環(huán)返回屬性鳖眼,默認值為true。
- 訪問器屬性不能直接定義嚼摩,必須使用Object.defineProperty()方法來定義
var book = {
_year:2004;
edition: 1;
};
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set: function(newValue){
if(newValue > 2004){
this._year = newValue;
this.edition+=newValue -2004;
}
}
};
book.year = 2005; //對訪問器屬性year進行寫入钦讳,因此調(diào)用setter函數(shù)
alert(book.edition); //2
2 創(chuàng)建對象
Object構(gòu)造函數(shù)和對象字面量
前面提到了早期創(chuàng)建對象的兩種方法:Object構(gòu)造函數(shù)和對象字面量矿瘦,但是這些方式有明顯的缺點:使用同一個接口創(chuàng)建很多對象,會產(chǎn)生大量的重復(fù)代碼愿卒。
工廠模式:
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
}
return o;
}
var person1 = create("Nicholas", 29, "Software Engineer");
缺點:
- 工廠模式雖然解決了創(chuàng)建多個相似對象會有大量重復(fù)代碼的問題缚去,但是卻沒有解決 對象識別 的問題(因為返回的都是Object類型)
構(gòu)造函數(shù)模式
ECMAScript中的構(gòu)造函數(shù)可以用來創(chuàng)建特定類型的對象。
function Person(name, age, job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
}
}
// 注意將Person當做構(gòu)造函數(shù)琼开,需要使用new操作符來創(chuàng)建新對象
var person1 = new Person("xin",22,"Software Engineer");
var person2 = new Person("wu",22,"Software Engineer");
在上面的例子中易结,person1和person2分別保存著一個不同的實例,但是這兩個對象都有一個constructor(構(gòu)造函數(shù))屬性柜候,該屬性執(zhí)行Person搞动。
檢測對象類型:
- 利用constructor屬性:
alert(person1.constructor == Person); //true
- 利用instanceof操作符:
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將它的事例標識為一種特定的類型
缺點:
- 每個方法都要在每個實例上重新創(chuàng)建一遍。上例中渣刷,person1和person2中都有一個sayName的方法鹦肿,但是兩個實例中的方法不是同一個Function的實例。因此不同實例上的同名函數(shù)是不相等的辅柴。
- 解決方法:把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部箩溃。
function Person(name, age, job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;//sayName屬性設(shè)置為全局的sayName函數(shù)
}
function sayName(){
alert(this.name);
}
var person1 = new Person("xin",22,"Software Engineer");
var person2 = new Person("wu",22,"Software Engineer");
-
修改后,person1和person2對象共享了在全局作用域中定義的同一個sayName()函數(shù)碌嘀。但是出現(xiàn)了新的問題:
在全局作用域中定義的函數(shù)(sayName)實際上只能被某個對象(person1 person2)調(diào)用涣旨,讓全局作用域“名不副實”;
如果對象需要定義很多方法股冗,則需要在全局作用域中定義很多全局函數(shù)开泽,是得這個 自定義的引用類型(自定義的構(gòu)造函數(shù)) 無封裝性可言。
解決方法:原型模式
原型模式
每個函數(shù)都有一個prototype(原型) 屬性魁瞪,這個屬性是一個指針穆律,指向一個對象,而這個對象的用途是 包含可以由特定類型的所有實例共享的屬性和方法 导俘。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.ptototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName();//"Nicholas"
//person1和person2的屬性和方法是所有實例共享的
alert(person1.sayName == person2.sayName); //true
理解原型對象
原型屬性[[Prototype]]的訪問
- 確定對象之間的關(guān)系 isPrototypeOf
alert(Person.prototype.isPrototypeOf(person1));//true
- 獲取原型對象 Object.getPrototypeOf()
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name);//Nicholas
- 每當代碼讀取某個對象的某個屬性時峦耘,都會執(zhí)行一次搜索,目標是具有給定名字的屬性旅薄。搜索 首先從對象實例本身開始 辅髓。如果在實例中找到了具有給定名字的屬性,則返回該屬性的值少梁。如果沒有找到洛口,則繼續(xù)搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性凯沪。如果在原型對象中找到了額這個屬性第焰,則返回該屬性的值。
- 雖然可以通過對象實例訪問保存在原型中的值妨马,但卻不能通過對象重寫原型中的值挺举。如果在實例中添加了一個屬性杀赢,且該屬性與實例原型中的一個屬性同名,則該屬性會屏蔽原型中的那個屬性湘纵。
- 可以使用hasOwnProperty()方法來檢測一個屬性是存在與實例中脂崔,還是存在與原型中。
person1.hasOwnProperty("name"); //false
- 原型與in操作符:單獨使用in操作符時梧喷,in操作符會在通過原型能夠訪問給定屬性時返回true砌左,無論該屬性存在與實例中還是存在原型中。同時使用hasOwnProperty()方法和in操作符铺敌,就可以確定該屬性到底是存在與對象中汇歹,還是存在與原型中。
alert( !person1.hasOwnProperty(name) && name in person1); //true
更簡單的原型語法:
前面的例子中每添加一個屬性和方法就要敲一遍Person.prototype适刀。為了減少不必要的輸入秤朗,更常見的方法是 用一個包含所有屬性和方法的對象字面量來重寫整個原型對象
function Person(){
}
Person.prototype = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
}
注意:
這里使用的語法煤蹭,本質(zhì)上完全重寫了默認的prototype對象笔喉,因此constructor屬性也就變成了新對象的constructor屬性(指向Object構(gòu)造函數(shù)),不再指向Person函數(shù)硝皂。使用instanceof操作符還能返回正確的結(jié)果常挚,但是通過constructor已經(jīng)無法確定對象的類型了。
var friend = new Person();
alert(friend.instanceof Person);//true
alert(friend.constructor == Person);//false
如果constructor屬性很重要稽物,可以將其設(shè)為適當?shù)闹?/p>
//方法一奄毡,但是會使constructor屬性的[[Enumerable]]特性變?yōu)閠rue
function Person(){
}
Person.prototype = {
constructor:Person,
name:"Nicholas",
age:29,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
}
function Person(){
}
Person.prototype = {
name:"Nicholas",
job:"Software Engineer",
age:29,
sayName:function(){
alert(this.name);
}
}
Object.defindProperty(Person.prototype, "constructor",{
enumerable: false,
value: Person
});
原生對象的原型:
所有原生類型(Object,Array,String,等等)都在其構(gòu)造函數(shù)的原型上定義了方法。
原型對象的問題:
- 省略了為構(gòu)造函數(shù)傳遞參數(shù)贝或,導(dǎo)致了所有實例在默認情況下都取得相同的屬性值吼过。
- 原型對象的最大問題是由其共享屬性 的本質(zhì)所導(dǎo)致的:
function Person(){
}
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
friends: ["Sheldon","Court"],
sayName: function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("van");
alert(person1.friends); //sheldon,court咪奖,van
alert(person2.friends); //sheldon盗忱,court,van
alert(person1.friends === person2.friends);//true
出現(xiàn)上述問題的原因在于:person1和person2的friends屬性共享一個數(shù)組羊赵。
組合使用構(gòu)造函數(shù)模式和原型模式
構(gòu)造函數(shù)模式用來定義實例屬性趟佃,原型模式用來定義方法和共享屬性。結(jié)果每個實例都會有自己的一份實例屬性的副本昧捷,但同時又共享著對方法的引用闲昭,最大限度的節(jié)省了內(nèi)存。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["sheldon","mary"];
}
Person.prototype = {
constructor: Person,
sayName: function(){
alert(this.name);
}
}
穩(wěn)妥構(gòu)造函數(shù)模式
- 特點:1.新創(chuàng)建對象的實例方法不引用this靡挥;2.不使用new 操作符調(diào)用構(gòu)造函數(shù)
function Person(name,age,job){
var o = new Object();
o.sayName = function(){
alert(name);
}
return o;
}
- 以這種方式創(chuàng)建的對象中序矩,除了使用sayName()方法之外,沒有其他辦法訪問name的值跋破。
var friend = Person("Nicholas",29,"Software Engineer");
friend.sayName();
即使有其他代碼會給這個對象添加方法或數(shù)據(jù)成員贮泞,但也不可能有別的方法訪問傳入到構(gòu)造函數(shù)中的原始數(shù)據(jù)(name,job,age)楞慈。穩(wěn)妥構(gòu)造函數(shù)模式提供的這種安全性,使得它非常適合在某些安全環(huán)境中執(zhí)行啃擦。
使用穩(wěn)妥構(gòu)造函數(shù)模式創(chuàng)建的對象與構(gòu)造函數(shù)之間沒什么關(guān)系囊蓝,因此instanceof操作符對這種對象沒有什么意義