Object類型是JavaScript中使用最多的一種類型。建立Object實例的方式有多種,接下來一一列舉。
1. Object構造函數
var person =
new Object();
person.name = "Brittany";
person.age = 23;
person.job = "web front-end engineer";
person.sayName =
function() {
console.log(
this.name);
};
person.sayName();
//
Brittany
2. 對象字面量模式
var person = {
name: "Brittany",
age: 23,
job: "web front-end engineer",
sayName:
function() {
console.log(
this.name);
}
};
person.sayName();
雖然Object構造函數或對象字面量均可以用來建立單個對象,但這些方式有個明顯的缺點:使用同一個接口建立不少對象,會產生大量的重複代碼。爲解決這個問題,可使用工廠模式的一種變體。web
3. 工廠模式
function createPerson(name, age, job) {
var o =
new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName =
function() {
console.log(
this.name);
};
return o;
}
var person1 = createPerson("Brittany", 23, "Software Engineer");
var person2 = createPerson("Sam", 26, "Software Engineer");
console.log(
typeof person1);
//
Object
工廠模式雖然解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。如代碼中只能檢測出person1爲Object類型。隨着JavaScript的發展,又一個新模式出現了。數組
4. 構造函數模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName =
function() {
console.log(
this.name);
}
}
var person1 =
new Person("Brittany", 23, "Web front-end engineer");
var person2 =
new Person("Closure", 26, "Manager");
person1.sayName();
person2.sayName();
console.log(person1.sayName == person2.sayName);
//
false
使用構造函數的主要問題:每一個方法都要在每一個實例上從新建立一遍。如代碼中所示,person1的sayName和person2的sayName不相等。能夠將函數定義轉移到構造函數外部來解決。函數
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(
this.name);
}
sayName函數的定義轉移到了構造函數外部。而在構造函數內部,咱們將sayName屬性設置成等於全局的sayName函數。這樣一來,因爲sayName包含的是一個指向函數的指針,所以person1和person2對象就共享了在全局做用域中定義的同一個sayName()函數。這的確解決了兩個函數作同一件事的問題,但是新問題又來了:在全局做用域中定義的函數實際上只能被某個對象調用,這讓全局做用域有點名存實亡。而更讓讓人沒法接受的是:若是須要定義不少方法,那就要定義不少個全局函數,因而這個自定義的引用類型就無封裝性可言。這些問題可經過使用原型模式來解決。this
5. 原型模式
1)對象建立
function Person() {}
Person.prototype.name = "Brittany";
Person.prototype.age = 23;
Person.prototype.job = "Web front-end engineer";
Person.prototype.getName =
function() {
console.log(
this.name);
};
var p1 =
new Person();
var p2 =
new Person();
console.log(p1.name);
//
Brittany
console.log(p2.name);
//
Brittany
console.log(p1.getName == p2.getName);
//
true
實例中建立的屬性會覆蓋原型中的同名屬性,不能修改原型中的屬性。spa
p1.name = "Susan";
console.log(p1.name);
//
Susan
hasOwnProperty()檢測一個屬性是否存在於實例中。prototype
console.log(p2.hasOwnProperty("name"));
//
false
p2.name = "koko";
console.log(p2.hasOwnProperty("name"));
//
true
delete p2.name;
console.log(p2.hasOwnProperty("name"));
//
false
isPropertyOf()設計
console.log(Person.prototype.isPrototypeOf(p1));
//
true
console.log(Person.prototype.isPrototypeOf(p2));
//
true
getPropertyOf()指針
console.log(Object.getPrototypeOf(p1) == Person.prototype);
//
true
console.log(Object.getPrototypeOf(p1));
//
Person
2)原型與in操做符
in單獨使用時,經過對象訪問到特定屬性時返回true,不管該屬性存在於實例中仍是原型中。hasOwnProperty(),經過對象訪問到特定屬性時返回true,且該屬性存在於實例中。 code
var p3 =
new Person();
console.log("name"
in p3);
//
true
console.log(p3.hasOwnProperty("name"));
//
false
p3.name = "insist";
console.log(p3.hasOwnProperty("name"));
//
true
肯定屬性究竟是存在於對象中,仍是存在於原型中。以下函數hasPrototypePropery()返回true表示該屬性存在於原型中,而不是存在於實例中。對象
function hasPrototypeProperty(object, name) {
return !hasOwnProperty("name") && (name
in object);
}
for..in循環,全部經過對象可以訪問的,可枚舉的(enumerated)屬性,既包括存在於實例中的屬性,也包括存在於原型中的屬性。
for(
var prop
in p1) {
console.log(prop);
//
name age job sayName
}
Object.keys(),ECMAScript5的方法,取得對象上全部可枚舉的屬性,接收一個對象做爲參數,返回值是一個包含全部可枚舉屬性的字符串數組。注意:Person.prototype也是對象。
var keys = Object.keys(Person.prototype);
console.log(keys);
//
["name age job sayName"]
var p1 =
new Person();
console.log(Object.keys(p1));
//
[]
p1.name = "get";
console.log(Object.keys(p1));
//
["name"]
Object.getOwnPropertyNames(),獲得全部實例屬性,不管它是否可枚舉。
var keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys);
//
["constructor", "name", "age", "job", "getName"]
var keys_p1 = Object.getOwnPropertyNames(p1);
console.log(keys_p1);
//
[]
3)更簡潔的原型語法
function Person() {}
Person.prototype = {
name: "Brittany",
age: 23,
job: "Web front-end engineer",
getName:
function() {
return
this.name;
}
};
var friend =
new Person();
console.log(friend
instanceof Person);
//
true
console.log(friend
instanceof Object);
//
true
console.log(friend.constructor == Person);
//
false
console.log(friend.constructor == Object);
//
false
在上面的代碼中,將Person.prototype設置爲等於一個對象字面量形式建立的新對象,最終結果相同。但有一個例外:constructor屬性再也不指向Person了。每建立一個函數,就會同時建立它的prototype對象,這個對象也會自動得到constructor屬性。而咱們在這裏使用的語法,本質上徹底重寫了默認的prototype對象,所以constructor屬性也就變成了新對象的constructor屬性(指向Object構造函數),再也不指向Person函數。此時儘管instanceof操做符還能返回正確的結果,但經過constructor已經沒法肯定對象的類型了。
經過以下方式,將constructor手動設置爲合適的值。
Person.prototype = {
constructor: Person,
name: "Brittany",
age: 23,
job: "Web front-end engineer",
getName:
function() {
console.log(
this.name);
}
};
4)原型的動態性
在原型中查找值的過程是一次搜索,所以咱們對原型對象所作的任何修改都可以當即從實例上反映出來——即便是先建立了實例後修改原型也照樣如此。
var friend =
new Person();
Person.prototype.sayHi =
function() {
console.log("hi");
};
friend.sayHi();
儘管能夠隨時爲原型添加屬性和方法,而且修改可以當即在全部對象實例中反映出來,但若是是重寫整個原型對象,狀況就不同了。
function Person() {}
var friend =
new Person();
Person.prototype = {
constructor: Person,
name: "Brittany",
age: 23,
job: "Web front-end engineer",
getName:
function() {
console.log(
this.name);
}
};
friend.getName();
//
error
如果建立實例放在重寫原型對象以後,則不會報錯。
5)原生對象的原型
全部原生引用類型(Object、Array、String)都在其構造函數的原型上定義了方法,如:Array.prototype.sort()、String.prototype.subString(), 經過原生對象的原型能夠取得全部默認方法的引用,並能夠定義新方法。
console.log(
typeof Array.prototype.sort);
//
function
console.log(
typeof String.prototype.substring);
//
function
String.prototype.startsWith =
function(text) {
return
this.indexOf(text) == 0;
};
var msg = "Hello World";
console.log(msg.startsWith("Hello"));
//
true
6)原型對象的問題
缺點一:省略了爲構造函數傳遞初始化參數這一環節,結果全部實例在默認狀況下將取得相同的屬性值。
缺點二:原型中全部屬性被許多實例共享,這種共享對於函數很是適合。對於包含基本值屬性倒也說得過去,由於經過在實例上添加一個同名屬性,能夠隱藏原型中對應的屬性。但對於包含引用類型值得屬性來講,問題比較突出。
function Person() { }
Person.prototype = {
constructor: Person,
name: "Brittany",
friends: ["pink", "judy", "sam"],
age: 23,
job: "Web front-end engineer",
getName:
function() {
console.log(
this.name);
}
};
var person1 =
new Person();
var person2 =
new Person();
person1.friends.push("leo");
console.log(person1.friends);
//
["pink", "judy", "sam", "leo"]
console.log(person2.friends);
//
["pink", "judy", "sam", "leo"]
console.log(Person.prototype.friends);
//
["pink", "judy", "sam", "leo"]
person1.age = 35;
console.log(person1.age);
//
35
console.log(person2.age);
//
23
console.log(Person.prototype.age);
//
23
person1的friends屬性修改影響了person2的friends,可是person1的age屬性修改並未影響person2的age屬性。
緣由在於:friends數組存在於Person.prototype中而非person1中,所以修改也會經過person2.friends(與person1.friends指向同一個數組)反映出來。而age屬性在person1中也存在一份,修改的age屬性只是修改person1中的,並不能修改Person.prototype中的age屬性。
6. 組合使用構造函數模式和原型模式
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。這樣,每一個實例都會有本身的一份實例屬性的副本,但又同時共享着對方法的引用。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["aa", "bb", "cc"];
}
Person.prototype = {
constructor: Person,
sayName:
function() {
console.log(
this.name);
}
};
var person1 =
new Person("Brittany", 23, "Web front-end Engineer");
person1.friends.push("dd");
//
["aa", "bb", "cc", "dd"]
console.log(person1.friends);
var person2 =
new Person("Sam", 26, "Web front-end Engineer");
console.log(person2.friends);
//
["aa", "bb", "cc"]
console.log(person1.friends == person2.friends);
//
false
console.log(person1.sayName == person2.sayName);
//
true
時間:2014-10-21
地點:合肥
引用:《JavaScript高級程序設計》