JavaScript中如何創建對象

JavaScript支持面向對象編程,但是不使用類或者接口,在沒有類的情況下,可以使用下列模式創建對象:
①工廠模式
②構造函數模式
③原型模式

如何創建自定義對象?

1、創建一個Object的實例

創建自定義對象最簡單的方式就是創建一個Object的實例,然後再爲它添加屬性和方法,如下所示:

var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";

person.sayName = function(){
	alert(this.name);
}	

2、對象字面量

var person = {
	name: "Nicholas",
	age: 29,
	job: "Software Engineer",

	sayName: function(){
		alert(this.name);
	}
};	

雖然Object構造函數或對象字面量都可以用來創建單個對象且語法非常簡單,但是這兩個方式有一個明顯的缺點:使用同一個接口創建很多對象,會產生大量重複代碼,爲了解決這個問題,我們引入下一個方式:

3、工廠模式

工廠模式抽象了創建具體對象的過程,考慮到在js中無法創建類,開發人員就發明了一種函數,用來封裝以特定接口創建對象的細節:

function createPerson(name, age, job){
	var o = new Object();
	o.name = name;
	o.age = age;
	o.sayName = function(){
		alert(this.name);
	};
	return o;
}

var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

優勢: 函數createPerson()能夠根據接受的參數來構建一個包含所有必要信息的Person對象。可以通過無數次調用這個函數來創建對象。
不足: 工廠模式雖然解決了創建多個相似對象的問題,但是沒有解決對象識別的問題(即怎樣知道一個對象的類型)。

4、構造函數模式

我們知道js中的構造函數可以用來創建特定類型的對象,像Object和Array這樣的原生構造函數,在運行時會自動出現在執行環境中。此外,也可以創建自定義的構造函數,從而定義自定義對象類型的屬性和方法。前面的例子可用構造函數模式重寫:

function Person(name, age, job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function(){
		alert(this.name);
	};
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

在這個例子中,Person()函數替代了createPerson()函數,Person()函數除了與createPerson()中相同的部分外,還存在以下的不同之處:

  • 沒有顯示地創建對象
  • 直接將屬性和方法給了this對象
  • 沒有return語句
    person1和person2的constructor屬性都指向Person

構造函數

構造函數與其它函數的唯一區別,就在於調用他們的方式不同。任何函數,只要通過new操作符來調用,它就可以作爲構造函數;而任何函數,如果不通過new操作符來調用,那它和普通函數也不會有什麼兩樣。
作爲構造函數調用:

var person =
new("Nicholas", 29, "Software Engineer");

作爲普通函數調用:

Person("Greg", 27,
"Doctor");

不同調用方式時候this的指向:

①作爲構造函數:

new操作符調用構造函數實際上會經歷以下步驟:
1、創建一個新對象
2、將構造函數作用域賦給新對象(一次this就指向了這個新對象)
3、執行構造函數中的代碼(爲這個而對象添加屬性)
4、返回新對象

②作爲普通函數:

當在全局作用域中調用一個函數時,this對象總是指向Global對象(在瀏覽器中就是window對象)。


優勢: 創建自定義的構造函數意味着將來可以將它的實例標識爲一種特定的類型,這正是構造函數模式勝過工廠模式的地方。(instanceof)

不足: 使用構造函數的主要問題,就是每個方法都要在每個實例上重新創建一遍,每個Person實例都包含一個不同的Function實例,然而創建兩個完成同樣任務的Funcition實例是沒有必要的

5、把函數轉移到構造函數之外

function Person(name, age, job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = sayName;
}
function sayName(){
	alert(this.name);	
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

優勢: 這樣一來,sayName包含的是一個指向函數的指針,因此person1和person2對象就共享了全局作用域中定義 一個sayName函數。
不足: 在全局作用域中定義的函數實際上只能被某個對象調用,這讓全局作用域有點名不副實。而更讓人無法接受的是:如果對象需要定義很多方法,那就要定義多個全局函數,於是我們這個自定義的引用類型就絲毫沒有封裝性可言了。

6、原型模式

prototype: 我們創建的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。如果按照字面意思來理解,那麼prototype就是通過調用構造函數而創建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象示例共享它所包含的屬性和方法。

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Soft Engineer";
Person.prototype.sayName = function(){
	alert(this.name)
};

var person1 = new Person();
var person2 = new Person();
person1.sayName();
person2.sayName();

①理解原型對象

無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性,這個屬性指向函數的原型對象。在默認情況下所有的原型對象都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在在函數的指針。也就是Person.prototype.constructor指向Person。
創建了自定義的構造函數之後,其原型對象默認只會取得constructor屬性,至於其他方法都是從object繼承而來的。當調用構造函數創建一個新的實例之後,該實例內部將包含一個指針(內部屬性),指向構造函數的原型對象。這個指針叫[[Prototype]],雖然在腳本中沒有標準的方式來訪問[[Prototype]],但Firefox、Safari和Chrome在每個對象上都支持一個屬性_proto_。但是要明確非常重要的一點:這個連接存在於實力與構造函數的原型對象之間,而不是存在於實例與構造函數之間。
在這裏插入圖片描述
Person的每個實例——person1和person2都包含一個內部屬性,該屬性僅僅指向了Person.prototype;換句話說,它與構造函數沒有直接的關係。雖然這兩個實例都不包含屬性和方法,但是我們卻可以調用person1.sayName(),這是通過查找對象屬性的過程來實現的:
每當代碼讀取某個對象的沒個屬性的時候,都會執行一次搜索,米表示具有給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中找到了具有給定名字的屬性,則返回該屬性的值瞭如果沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性。

②屏蔽

當爲對象實例添加一個屬性的時候,這個屬性就會屏蔽原型對象中保存的同名屬性;換句話說,添加這個屬性只會阻止我們訪問原型中的那個屬性,但不會修改那個屬性。即使將這個屬性設置爲null,也只會在實例中設置這個屬性,而不會恢復其指向原型的連接。
不過,使用delete操作符則可以完全刪除實例屬性,從而讓我們能夠重新訪問原型中的屬性。

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Soft Engineer";
Person.prototype.sayName = function(){
	alert(this.name)
};

var person1 = new Person();
var person2 = new Person();

person1.name = "Greg"
console.log(person1.name);
console.log(person2.name);

delete person1.name;
console.log(person1.name);
console.log(person2.name);

在這裏插入圖片描述

③更簡單的原型語法

你也許注意到了,前面例子中每添加一個屬性和方法就要敲一遍Person.prototype。爲了減少不必要的輸入,也爲了從視覺上更好地封裝原型的功能,更常見的做法是用一個包含所有屬性和方法的對象字面量來重寫整個原型對象。

funciton Person(){}
Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "Software Engineer"
	sayName: fucntion(){
		alert(this.name);
	}
};	

用以上方法創建對象,最終結果相同,但有一個例外:constructor屬性不再指向Person了。我們用對象字面量的語法,本質上完全重寫了默認的prototype對象,因此constructor屬性就變成了新的對象的constructor屬性(指向Object構造函數),不再指向Person函數。(此時儘管instanceof操作符還能返回正確的結果,但通過constructor已經無法確定對象的類型了)

④原型的動態性

function Person(){
}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Soft Engineer";
Person.prototype.sayName = function(){
	alert(this.name)
};

var friend = new Person();

Person.prototype.sayHi = function(){
	alert("Hi");
};

friend.sayHi();

原型中查找值的過程。實例與原型之間的鏈接是一個指針,而非一個副本。
但如果是重寫了整個原型對象,那情況就不一樣了,這相當於切斷了構造函數與最初原型對象之間的聯繫。請記住:實例中的指針僅指向原型,而不指向構造函數。

funciton Person(){}

Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "Software Engineer"
	sayName: fucntion(){
		alert(this.name);
	}
};	

var friend = new Person();

Person.prototype.sayHi = function(){
	alert("Hi");
};

friend.sayHi();

在這裏插入圖片描述

⑤原型對象的問題

  • 原型對象省略了爲構造函數傳遞參數這一環節,結果所有實例在默認情況下都會取得相同的屬性值。
  • 原型對象的共享性
funciton Person(){}

Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "Software Engineer",
	friends: ["a", "b"],
	sayName: fucntion(){
		alert(this.name);
	}
};	

var person1 = new Person();
var person2 = new Person();

person1.friends.push("c");

console.log(person1.friends);
console.log(person2.friends);
console.log(person1.friends == person2.friends)

⑥組合使用構造函數模式和原型模式

funciton Person(name, age, job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.friends = ["a", "b"];
}

Person.prototype = {
	constructor: Person,
	sayName: function(){
		alert(this.name)
	}
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("c);
console.log(person1.friends);
console.log(person2.friends);
cosnole.log(person1.friends === person2.friends);
console.log(person1.sayName === person2.sayName);

小結

  • 工廠模式:使用簡單的函數常見對象,爲對象添加屬性和方法,然後返回對象,這個方法後來被構造函數模式所取代
  • 構造函數模式:可以創建自定義引用類型,可以向創建內置對象實例一樣使用new操作符。但是構造函數的每個成員都無法複用,包括函數。由於函數可以不侷限於對象,因此沒有理由不在多個對象之間共享函數。
  • 原型模式:使用構造函數的prototype屬性來指定哪些應該共享的屬性和方法。組合使用構造函數模式和原型模式時,使用構造函數定義實例屬性,而使用原型定義共享的屬性和方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章