##**理解對象**##
---
###**屬性類型**
> ?JavaScript中有兩種屬性類型 分別是 數(shù)據(jù)屬性和訪問器屬性
#### **數(shù)據(jù)屬性**
數(shù)據(jù)屬性具有四個特征值
[Configurble] 表示能否修改特性或者是通過delete重新定義屬性 默認(rèn)為true
[Enumerable] 表示能否通過forin遍歷循環(huán)返回屬性
[Writable] 表示能否修改這個屬性值 默認(rèn)為true
[Value] 包含這個屬性的數(shù)據(jù)值
> 如果要修改屬性的特征值 那么可以通過以下的方法
```
var person = {
? ?name:"zhang"
}
Object.defineProperty(person,"name",{
? ?writable:false,
? ?value:"zhao" //小寫
});
person.name = "wang";
console.log(person.name)
```
> 但要注意的是如果你修改了屬性的Configurble 你將無法再修改這個屬性的特性犁跪。
####**訪問器屬性**
> 訪問器屬性不包含數(shù)據(jù)值陷猫,它們包含一塊getter和setter函數(shù)黎做,在讀取和調(diào)用時會調(diào)用這些函數(shù)啡专,從而負(fù)責(zé)返回 和 處理苹享。訪問器有下面四個特性阳谍。
訪問器有下面四個特性采记。
> [Configurble] 表示能否修改特性或者是通過delete重新定義屬性 默認(rèn)為true
? [Enumerable] 表示能否通過forin遍歷循環(huán)返回屬性
? [Get] 在讀取時調(diào)用的函數(shù) ,默認(rèn)為undefined
? [Set] 在寫入時調(diào)用的函數(shù)拉一, 默認(rèn)為undefined
```
var person = {
? ?name:"zhang",
? ?_year:2004,
? ?edition:1
}
Object.defineProperty(person,"year",{
? ?get: function () {
? ? ? ?return this._year;
? ?},
? ?set: function (newValue) {
? ? ? ?if(newValue > 2004){
? ? ? ? ? ?this._year = newValue; //這里的設(shè)置新值 設(shè)置的是_year的值
? ? ? ? ? ?this.edition += newValue - 2004;//每次更新同時去設(shè)置版本
? ? ? ?}
? ?}
});
//這里的year就是一個典型的**訪問器屬性**采盒,他不保存實(shí)際的value 但是有擁有g(shù)etset方法,他只能通過這種方法去定義蔚润。而數(shù)據(jù)函數(shù)則是修改特性需要調(diào)用磅氨,生成是不需要的
person.year = 2005; //其實(shí)是調(diào)用了year屬性的set方法 然后實(shí)質(zhì)上設(shè)置了_year的值 并且跟新版本
alert(person.year); //調(diào)用year的get方法 返回了_year的值
alert(person.edition);
//所以實(shí)質(zhì)上 _year 和 year 是兩個完全不同的值.這是訪問器屬性的常見用法 get set一個屬性 影響其他屬性。
```
####**定義多個屬性**
> 在一些情況下我們需要為一個對象的多個屬性設(shè)置特性嫡纠,當(dāng)然js也為我們提供了方便的方法 `Object.defineProperties()`.它提供了兩個參數(shù)烦租,第一個參數(shù)代表了你要修改的對象延赌,第二個參數(shù)也是一個對象 其中的屬性和第一個對象中需要修改的一一對應(yīng)
```
var cat = {};
Object.defineProperties(cat,{
? ?_year:{
? ? ? ?writable:true,
? ? ? ?value:2004
? ?},
? ?edtions:{
? ? ? ?wirtable:true,
? ? ? ?value:1
? ?},
? ?year:{
? ? ? ?get: function () {
? ? ? ? ? ?return this._year;
? ? ? ?},
? ? ? ?set: function (newValue) {
? ? ? ? ? ?if(newValue > 2004){
? ? ? ? ? ? ? ?this._year = newValue; //這里的設(shè)置新值 設(shè)置的是_year的值
? ? ? ? ? ? ? ?this.edition += newValue - 2004;//每次更新同時去設(shè)置版本
? ? ? ? ? ?}
? ? ? ?}
? ?}
});
alert(cat.year);
alert(cat.edtions);
```
####**讀取屬性的特性**
> 既然有定義方法,自然有讀取方法 `Object.getOwnPropertyDescriptor()` 獲得自身特性描述符叉橱,返回一個具有四個屬性的對象挫以,讀取不同對象的時候,他的屬性也不一樣。具體看上面的列表赏迟。
###**創(chuàng)建對象**
> 雖然用Obj構(gòu)造函數(shù)或?qū)ο笞置媪靠梢詣?chuàng)建單個對象屡贺,但是當(dāng)你需要創(chuàng)建大量對象的時候蠢棱,就會非常的麻煩并且產(chǎn)生大量重復(fù)的代碼锌杀。
####**工廠模式**
> 用函數(shù)來封裝特定接口創(chuàng)建對象,但這種方法無法知道他屬于什么類型的對象
```
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 tom = createPerson("tom",19,"doctor");
```
####**構(gòu)造函數(shù)模式**
> 我們都知道泻仙,我們可以通過構(gòu)造函數(shù)快捷構(gòu)造特定的對象 比如Object和Array糕再,此外,我們可以創(chuàng)建自己的構(gòu)造函數(shù)玉转。
```
function Person(name,age,job) {
? ?this.name = name;
? ?this.age = age;
? ?this.job = job;
? ?this.sayName = function () {
? ? ? ?alert(name);
? ?}
}
var tom = new Person("tom",90,"doctor");
var jerry = new Person("jerry",18,"teacher");
```
> 以上是一個典型的構(gòu)造函數(shù) 我們可以和工廠模式做一個簡單的對比
> 第一 沒有顯示的創(chuàng)建對象
> 第二 直接通過this來給對象添加屬性
> 第三 沒有通過return把對象返回
> 第四 構(gòu)造函數(shù)的首字母應(yīng)大寫
既然生成了構(gòu)造函數(shù)突想,我們可以通過new關(guān)鍵字來調(diào)用這個構(gòu)造函數(shù),當(dāng)new的時候 其實(shí)發(fā)現(xiàn)了以下步驟
> 1. 創(chuàng)建了一個新的對象
> 2. ?把這個構(gòu)造函數(shù)的作用域連接到這個新生成的對象 也就是說 這個函數(shù)內(nèi)的this 指向新對象
> 3. ?執(zhí)行構(gòu)造函數(shù)的代碼(添加屬性和方法)
> 4. ?返回構(gòu)造完畢的對象
在上方的例子中tom和jerry分別保存了一個**Person的一個實(shí)例** 他們其實(shí)都有一個constructor(構(gòu)造函數(shù))屬性究抓,該屬性指向了他們的**構(gòu)造函數(shù)Person**.
但實(shí)質(zhì)上這種方法可能并不是非常的保險猾担,我們可以使用更加可靠的`instanceof`操作符 就像下面這樣
```
console.log(tom instanceof Person);//true
console.log(tom instanceof Object);//true
console.log(jerry instanceof Person);//true
console.log(jerry instanceof Object);//true
//所有對象都繼承自O(shè)bject
```
**構(gòu)造函數(shù)模式相比工廠模式最大的優(yōu)勢在于他提供了一個訪問對象類型的方法**
> 構(gòu)造函數(shù)也是函數(shù),這意味這個函數(shù)能夠被new的方式調(diào)用,同樣可以被當(dāng)成普通的方式來調(diào)用刺下,在普通情況下 他的所有特征會和普通函數(shù)表現(xiàn)的完全一樣绑嘹。以下是一個簡單的例子。
```
Person("drivd",66,"ko");
console.log(window.name);//drivd
var cat = {};
Person.call(cat,"cat",10,"catch mourse");
console.log(cat.name);
//或者使用以下的方法 在對象內(nèi)把函數(shù)置入到對象之中
cat.fnc = Person;
cat.fnc("wang",15,22);
```
> 首先是一個在全局內(nèi)調(diào)用這個Person函數(shù)的情況下 Person函數(shù)正常執(zhí)行 這里的**this指向了**運(yùn)行他的作用域?qū)ο?也就是**window對象**橘茉。所以window對象的name就被設(shè)置成了drivd.
> 第二是在其他對象的作用域情況下工腋,通過call方法來增加作用域 進(jìn)入函數(shù)時**this指向?yàn)閏at對象**。
**構(gòu)造函數(shù)的問題**
```
this.sayName = function () {
? ? ? ?alert(name);
? ?}
```
> 在創(chuàng)建實(shí)例時畅卓,都會調(diào)用構(gòu)造函數(shù)內(nèi)的方法擅腰,但是每次在構(gòu)造方法的時候,它實(shí)質(zhì)上是**創(chuàng)建一個新的函數(shù)**(也就是一個對象)**然后讓sayName指向這個new出來的函數(shù)**翁潘,不同的Person實(shí)例中的方法雖然函數(shù)名和做的事情完全相同趁冈,但他們并不指向同一個函數(shù)對象。所以當(dāng)你比較(==)兩個相同類型不同實(shí)例的相同方法時拜马,**他們并不相同**渗勘。
>
> 所以我們需要一種能夠把一些函數(shù)封裝 然后在構(gòu)造函數(shù)內(nèi)部可以直接引用,這樣就可以讓不通實(shí)例的相同方法的指針指向同一個函數(shù)一膨。也許把方法寫在全局變量之中呀邢,在內(nèi)部調(diào)用是種方法。他達(dá)到了兩者方法里面存放一個指針 并且指向了同一個函數(shù)豹绪,但這種方法的**封裝性**實(shí)在差勁价淌,并且并不是很好管理(如果需要非常多函數(shù)的情況下)申眼。
####**原型模式**
>我們所創(chuàng)建的所有對象都擁有著一個屬性`prototype`,他包含了一個指針蝉衣,指向了這個對象的原型對象括尸,而這個原型對象的作用是提供一種類型的不同實(shí)例所共享的方法和屬性。讓我們接下來深入了解原型的本質(zhì)病毡。
**理解原型的本質(zhì)**
> 首先當(dāng)我們創(chuàng)建的一個新的函數(shù)濒翻,馬上按特定規(guī)則創(chuàng)造一個當(dāng)前函數(shù)的`prototype`屬性,這個屬性指向了函數(shù)的原型對象啦膜。而原型對象也會生成`constructor`屬性有送,他指向了prototype屬性所在的函數(shù)。而在沒有實(shí)例生成的時候 這個原型屬性里其實(shí)除了`constructor`沒有任何東西
>
> 第二步發(fā)生在實(shí)例調(diào)用構(gòu)造函數(shù)的過程中僧家,創(chuàng)建的實(shí)例時雀摘,實(shí)例內(nèi)部也會產(chǎn)生一個prototype屬性,然后內(nèi)部產(chǎn)生一個指針八拱,他指向了構(gòu)造函數(shù)所指向的原型對象阵赠,(盡管實(shí)例的這個屬性對腳本不可見,不可直接訪問)肌稻。要注意的是清蚀,這個連接存在于實(shí)例與原型對象之間,這連接與構(gòu)造函數(shù)并無太大關(guān)系爹谭。
>
> 對于實(shí)例可以用`Person.prototype.isPrototypeOf(Person1)` 的方法來查看是否和某個構(gòu)造函數(shù)的原型建立了鏈接枷邪, 如果兩者確實(shí)有鏈接,返回true,
>
> 在EMCAScript中新增加了一個方法 它能讓我們快速的返回一個對象的原型對象 `Object.getPrototypeOf(person1)`
>
> 當(dāng)實(shí)例的屬性被我們讀取時旦棉,他首先訪問自己本身有沒有這個屬性齿风,如果沒有的情況下,就繼續(xù)查詢他的原型绑洛,如果在原型中找到了這個屬性救斑,他的值就會被返回。
>
> 如果在某個實(shí)例的自身和他的原型中有同名的屬性真屯,那么自身的屬性將會屏蔽原型的屬性脸候。所以即使刪除這個屬性,修改的也永遠(yuǎn)是其自身的屬性绑蔫,他不會修改原型的屬性运沦,我們可以通過刪除對象自身的某個屬性,來讓取消對原型屬性的屏蔽
>
> `person1.hasOwnProperty("name")`繼承自O(shè)bject 他會檢查參數(shù)是否是對象其中的一個屬性配深,他不會去檢查對象的原型携添。
```
function Person() {
}
Person.prototype.name = "wang";
Person.prototype.age = 20;
Person.prototype.job = "T";
Person.prototype.sayName = function () {
? ?alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
person1.name = "zhang";
person1.sayName();//這里的sayName會查找到原型并且原型方法內(nèi)的this指向?yàn)閜erson1
alert(person1.hasOwnProperty("name")); //true
```
**原型與in操作符**
> 其實(shí)在之前我們就在forin中使用了in操作符 其實(shí)在檢測某個屬性是否屬于某個對象的時候,這個操作符同樣可以使用篓叶,他的特性與`hasOwnProperty()`不同烈掠,如果該屬性屬于該對象的原型對象羞秤,同樣會返回true,如果原型不曾擁有左敌,返回false瘾蛋。
>
> 現(xiàn)在我們有了檢測屬性的兩種方法 1 檢測屬性是否屬于對象本身 2 檢測屬性對象是否屬于對象本身或原型。那么我們?nèi)鄙俚谌N矫限。檢測對象是否屬于原型哺哼。其實(shí)實(shí)現(xiàn)的方法很簡單 無法就是達(dá)成了兩個條件 **第一** hasOwnProperty()返回false,該屬性在本身不存在叼风,**第二** in操作符返回true取董,在原型中存在。所以以下就是一個簡單方法
```
function hasPrototypeProperty(obj,name) {
? ?return !obj.hasOwnProperty(name) && (name in obj);//如果條件都達(dá)成 返回true
}
```
> 在我們通過forin循環(huán)遍歷屬性的時候咬扇,原型中的屬性也會被遍歷出來甲葬,(除非原型中屬性的特效標(biāo)記為無法遍歷)廊勃,但是這一點(diǎn)在IE8以一下版本中有一個BUG绳泉,**屏蔽不可枚舉屬性的實(shí)例屬性不會出現(xiàn)在forin循環(huán)之中**崭捍,也就是說,如果原型中的某個方法屏蔽in循環(huán),然后在實(shí)例自身中覆蓋同名方法耳鸯,連這個方法自身也會屏蔽In.無法發(fā)現(xiàn)。
>要取得一個對象中所有可以通過forin循環(huán)的屬性节槐,可以通過`Object.keys()`方法褐奴,他接受一個對象參數(shù),返回一個包含所有可forin遍歷的屬性標(biāo)識符字符串?dāng)?shù)組溉卓。他的順序是forin中的出場順序
>
>如果想取得一個對象內(nèi)的所有實(shí)例屬性 可以使用`Object.getOwnPropertyNames(Person)`他的參數(shù)是一個對象皮迟,他的返回值會包含非常多的屬性,比如caller argunments coustrusta
>當(dāng)然上方的返回值都只會是自身的屬性桑寨,不會去查找他們的原型伏尼。
**更簡單的原型語法**
在很多時候重復(fù)的調(diào)用.prototype屬性的封裝性并不是很好,我們可以通過創(chuàng)建一個新對象并設(shè)置的方法來
```
function Person() {
}
Person.prototype = {
? ?name:"null",
? ?age:0,
? ?job:"job"
? ?sayName:function () {
? ? ? ?alert(this.name);
? ?}
}
```
這個方法 唯一問題在于 Person的原型的constructor不再指向Person構(gòu)造函數(shù)尉尾,因?yàn)槲覀儗?shí)質(zhì)上創(chuàng)建了一個新的對象并把構(gòu)造函數(shù)的prototype指針指向了他爆阶,之前默認(rèn)創(chuàng)建的prototype對象已經(jīng)被覆蓋,新對象里也沒有constructor這個屬性沙咏,辦法就是在新的原型對象內(nèi)重新指定一下constructor
**原型的動態(tài)性**
原型的動態(tài)性主要體現(xiàn)在增加和重定向兩個方面
```
function Person() {
}
var person1 = new Person();
Person.prototype.name = "person1";
alert(person1.name);
```
> 上面的例子首先創(chuàng)建一個person的實(shí)例person1 然后給person的原型添加的name屬性辨图。即使增加屬性在創(chuàng)建實(shí)例之后,但依舊可以正常訪問原型中的屬性肢藐。這是因?yàn)楂@取值只是查詢故河,他在自身內(nèi)沒找到就回到原型中去找,找到了吆豹,返回鱼的。
```
Person.prototype = {
? ?name:"name"
}
alert(person1.name);//undefined 因?yàn)樗L問的仍舊是創(chuàng)建時綁定的原型對象杉女。
```
> 但是在這種情況下,就會發(fā)生無法訪問的情況鸳吸。因?yàn)閷?shí)例原型的連接出現(xiàn)在構(gòu)造函數(shù)的那一刻熏挎,他被綁定成原來的原型對象,而后來的原型對象被重新更改成另一個新創(chuàng)建的對象晌砾,也就是說坎拐,這個實(shí)例對象的原型仍舊是剛開始創(chuàng)建的那個原型對象。 和上面的例子對比养匈,一個是在新的原型對象內(nèi)創(chuàng)建屬性哼勇,而另一個則是在原本的原型中去添加屬性。
**原生對象的原型**
不僅僅是自創(chuàng)建的對象呕乎,就連很多原生對象(比如Object Array String)都使用了原型的方式來封裝方法积担,通過原生對象的原型不僅可以獲取原生方法的應(yīng)用,也可以為原生對象添加新的方法猬仁。
```
String.prototype.startsWith = function (tex) {
? ?return this.indexOf(tex) == 0;
}
var text = "Hello World";
alert(text.startsWith("World"));
```
以上就為基本包裝類的原型添加了一個方法
**原型對象的問題**
原生模式其實(shí)也存在一些問題帝璧,首先他沒有傳參數(shù)然后初始化的環(huán)節(jié),也就是說原型中的值默認(rèn)值都相同而且會被共享湿刽。假設(shè)我們兩個實(shí)例的原型相同的烁,且內(nèi)部有一個數(shù)組,被兩個共享诈闺,那么我們操作其中一個實(shí)例的該數(shù)組渴庆,會改變到另外一個實(shí)例的該數(shù)組。(如果重新創(chuàng)建 則創(chuàng)建在對象內(nèi)部 和原型無關(guān))
**組合使用構(gòu)造函數(shù)和原型模式**
構(gòu)造函數(shù)用于定義實(shí)例屬性雅镊,而原型模式用來定義需要方法和共享的屬性
```
function Person(name,age) {
? ?this.name = name;
? ?this.age = age;
? ?this.friends = ["tom","jerry"]
}
Person.prototype = {
? ?sayName:function () {
? ? ? ?alert(this.name);
? ?}
}
var person1 = new Person("wang",15);
var person2 = new Person("zhang",12);
```
**動態(tài)原型模式**
如果把分離的原型和構(gòu)造函數(shù)可能會造成一些困惑襟雷,可以把初始化原型和構(gòu)造函數(shù)封裝在一起
```
function Person(name,age) {
? ?this.name = name;
? ?this.age = age;
? ?this.friends = ["tom","jerry"]
? ?if(this.sayName != "function"){
? ? ? ?Person.prototype.sayName = function () {
? ? ? ? ? ?alert(this.name);
? ? ? ?}//不能調(diào)用對象字面量的方式去重寫原型,只能增加方法,不然第一個創(chuàng)建的對象會被隔絕
? ?}
}
```
> 以上的原型初始化只會執(zhí)行一次仁烹,在第一次調(diào)用構(gòu)造函數(shù)生成實(shí)例時耸弄,this指向的是new出來的對象,這個對象有原型晃危,但這個原型內(nèi)部沒有sayName函數(shù)叙赚。所以會執(zhí)行一次初始化,而第二次開始new出來的對象原型中已經(jīng)有sayName函數(shù)僚饭,以后就不會調(diào)用了震叮,