JS對象的創建與訪問
1.對象創建的3中方法
1.1.對象字面量
1 var obj = { 2 name: "mingzi", 3 work: function () { console.log("working...") }, 4 _age: 18, //下劃線開頭表示該屬性不建議在外部被直接訪問,但是任然可以被訪問,除非使用類或函數定義 5 //age: 18,使用下劃線定義屬性後,對象上會默認生成這樣一個沒有下劃線的屬性引用,指向同一個值,用來被外部訪問 6 get age(){ 7 console.log("get方法被調用");8 return this._age; //這裏不能是this.age,否則會陷入死循環 9 }, 10 set age(val){ 11 console.log("set方法被調用"); 12 if (val > 100 || val < 0) { 13 throw new Error("invalid value"); 14 } else { 15 this._age = val;//這裏也一樣,不能是this.age 16 }17 }, 18 address: { 19 home: "dingxi", 20 office: "xian" 21 } 22 } 23 24 console.log(obj.age); //這裏如果寫成obj._age,則屬性訪問不經過get方法 25 obj.age = 20;//同樣,這裏寫obj._age也不經過set方法,但能設置成功 26 console.log(obj.age);
使用get/set的好處就是可以對值進行邏輯判斷,這種方法定義的對象並不能實現Java中的private私有化效果。
在對多層嵌套對象的屬性級聯訪問時,要對中間屬性判空,除了多個if 判斷之外,還可以這麽寫:
1 var result = obj && obj.address && obj.address.home; //這種方式,只要中途有任何一個地方為null或undefined,則result被賦值為空,否側賦值為最後一個。
註意:沒有這個屬性的時候會返回undefined,有屬性沒值的時候才會返回null。
1.2. var obj = new Object()
與第一種創建的對象是一樣的,因為其構造器一樣。
構造器結構圖:
1.3.Object.defineProperties方式
可以定義屬性配置項,不常用
2.對象工廠
加入我們需要創建兩個具有相同結構的對象,你可能會這麽做:
1 var a = { 2 name: "zhangsan", 3 age: 12 4 } 5 var b = a; 6 console.log(a === b);//true 7 b.age = 13; 8 console.log(a);//{ name: ‘zhangsan‘, age: 13 }
但是,顯然這麽做並不能創建一個具有相同結構的對象b,因為只是復制了一個棧中的引用,堆中的對象是一樣的,修改了b.age也就修改了a.age,並不能達到復制對象的效果。
除非復制代碼,但這不是程序員應該做的事,所以我們就可以使用對象工廠:
1 function PersonFactory(name, age) { 2 3 return { 4 name: name, 5 age: age 6 } 7 } 8 9 var p1 = PersonFactory("zz",13); 10 var p2 = PersonFactory("zz",13); 11 console.log(p1 === p2); //false
通過一個工廠方法,就可以創建具有相同結構的不同對象了,工廠方法每次被調用,就相當於“復制”了一份代碼,達到了復制對象的效果。
工廠方法的缺點是:
生產出來的對象雖然結構一樣,但他們之間是完全獨立的個體,並不是像Java一樣的父對象與子對象之間的繼承關系,每個對象占用一份獨立的內存,無法共用。
3.構造函數
雖然JS是一門面向對象語言,但是並沒有像Java一樣的類(class)的概念
因為面向對象的三大特征是繼承、封裝、多態,並沒有類、接口等
對象是一種運行時的構造,類是一種開發時的構造、類和接口只是一種實現面向對象的手段。
所以不同語言在面向對象的本質上是一樣的,但實現方式可能有所不同,為了滿足需求,JS也通過構造函數的形式實現了類:
1 //構造函數名默認大寫,以作區分,可以看作是一個類 2 function Person(name, age) { 3 //私有的不同屬性直接掛載在this上,指向創建的對象 4 this.name = name; 5 this.age = age; 6 } 7 //共有的屬性掛載在對象原型上,就像父對象 8 Person.prototype.address = "xian"; 9 10 var p1 = new Person("xx",13); 11 var p2 = new Person("yy",14); 12 console.log(p1);//Person { name: ‘xx‘, age: 13 } 13 console.log(p1.address); //xian 14 console.log(p2.address); //xian
構造方法和工廠方法除了可以設置對象原型的屬性外效果是一樣的,但後者顯然更高端一點。
4.原型模式
4.1.原型的定義
無論什麽時候,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個 prototype屬性,這個屬性指向函數的原型對象。在默認情況下,所有原型對象都會自動獲得一個 constructor
(構造函數)屬性,這個屬性包含一個指向 prototype 屬性所在函數的指針。它們與構造函數沒有直接的關系。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,
則繼續搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性。如果在原型對象中找到了這個屬性,則返回該屬性的值。也就是說,在我們調用 person1.sayName() 的時候,會先後執行兩次搜
索。
雖然可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。
當為對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性;換句話說,添加這個屬性只會阻止我們訪問原型中的那個屬性,但不會修改那個屬性。即使將這個屬性設置為 null ,也
只會在實例中設置這個屬性,而不會恢復其指向原型的連接。不過,使用 delete 操作符則可以完全刪除實例屬性,從而讓我們能夠重新訪問原型中的屬性。
4.2更簡單的原型語法
1 function Person(){ 2 3 } 4 Person.prototype = { 5 name : "Nicholas", 6 age : 29, 7 job: "Software Engineer", 8 sayName : function () { 9 alert(this.name); 10 } 11 };
在上面的代碼中,我們將 Person.prototype 設置為等於一個以對象字面量形式創建的新對象。最終結果相同,但有一個例外: constructor 屬性不再指向 Person 了,而是指向Object,不過可以再設置回來。
4.3原型的動態性
可以隨時為原型添加屬性和方法,並且修改能夠立即在所有對象實例中反映出來,但如果是重寫整個原型對象,那麽情況就不一樣了。我們知道,調用構造函數時會為實例添加一個指向最初原型[[Prototype]] 指針,而把原型修改為另外一個對象就等於切斷了構造函數與最初原型之間的聯系。請記住:實例中的指針僅指向原型,而不指向構造函數。
4.4原型對象的問題
1 function Person(){ 2 3 } 4 Person.prototype = { 5 constructor: Person, 6 name : "Nicholas", 7 age : 29, 8 job : "Software Engineer", 9 friends : ["Shelby", "Court"], 10 sayName : function () { 11 alert(this.name); 12 } 13 }; 14 var person1 = new Person(); 15 var person2 = new Person(); 16 person1.friends.push("Van"); 17 alert(person1.friends); //"Shelby,Court,Van" 18 alert(person2.friends); //"Shelby,Court,Van" 19 alert(person1.friends === person2.friends); //true
即引用類型的原型屬性一般來說是私有的,與原型的共享性沖突。
4.5組合使用構造函數模式和原型模式
創建自定義類型的最常見方式,就是組合使用構造函數模式與原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每個實例都會有自己的一份實例屬性的副本,但同時又共享著對方法的引用,最大限度地節省了內存。另外,這種混成模式還支持向構造函數傳遞參數;可謂是集兩種模式之長。下面的代碼重寫了前面的例子。
1 function Person(name, age, job){ 2 this.name = name; 3 this.age = age; 4 this.job = job; 5 this.friends = ["Shelby", "Court"]; 6 } 7 Person.prototype = { 8 constructor : Person, 9 sayName : function(){ 10 alert(this.name); 11 } 12 } 13 14 var person1 = new Person("Nicholas", 29, "Software Engineer"); 15 var person2 = new Person("Greg", 27, "Doctor"); 16 person1.friends.push("Van"); 17 alert(person1.friends); //"Shelby,Count,Van" 18 alert(person2.friends); //"Shelby,Count" 19 alert(person1.friends === person2.friends); //false 20 alert(person1.sayName === person2.sayName); //true
這種構造函數與原型混成的模式,是目前在 ECMAScript中使用最廣泛、認同度最高的一種創建自定義類型的方法。可以說,這是用來定義引用類型的一種默認模式,這便是JS的偽類模式。
關於原型對象一節,詳見《JavaScript高級程序設計(第三版)》148頁,寫的巨好。
JS對象的創建與訪問