類 Class 類的概念應該是面嚮對象語言的一個特點,可是JavaScript並不像Java,C++等高級語言那樣擁有正式的類,而是多數經過構造器以及原型方式來仿造實現。在討論構造器和原型方法前,我能夠看看一種叫作工廠方式的仿造方法。html
<script> function material() { //工廠模式 alert('steel'); } function createCar(name, color) { var car = {}; car.name = name; car.color = color; car.material = material; return car; //相似調用函數,必需要有返回值 } var car1 = createCar('BMW', '白色'); var car2 = createCar('Benz', '黑色'); console.log(car1); console.log(car2); </script>
這種方式顯然能夠實現class的功能,可是外形上怎麼也沒法說它是個class以及class實例的建立過程。所以,出現了「構造函數方式」,它的關鍵在於構造器(Constructor)概念的引入。函數
構造器(Constructor)this
<script> function material() { //公共部分 alert('stell'); } //constructor 實例化時,此函數將會做爲實例化後對象的構造函數 //**構造函數名首字母最好大寫,以區分和普通函數** function CreateCar(name, color) { this.name = name; this.color = color; this.material = material; } var car1 = new CreateCar('BMW', '白色'); var car2 = new CreateCar('BenZ', '黑色'); console.log(car1); console.log(car2); console.log("\n"); </script>
這個看起來有點類的樣子了吧(先不提那個難看的外置function)?咱們發現,那個constructor
其實就是一個簡單的function,它與「工廠方式」中的createCar()區別就在於:
一、方法名大寫
二、沒有了空對象的建立和返回
三、使用this作引用。
那原來的那個空對象的建立以及返回的步驟去哪了呢?這兩個步驟,如今都由建立實例時的「new」實現了。「new」這個操做符負責建立一個空對象,而後將那個叫作構造器的function添加到實例對象中並觸發它,這樣這個function實際上就是這個對象的一個method,function中的this指向的即是這個對象,最後將這個對象返回。根據如上分析,咱們能夠把這個過程簡單分解爲以下代碼:spa
<script> var obj = {}; obj.constructor = CreateCar; obj.constructor("BMW", "白色"); //」this「 refers to obj return obj; </script>
構造函數方式
雖然與高級面嚮對象語言中的類建立方式已經很接近(使用new
建立),可是貌似那個遊離在類以外的function material()
其實倒是個至關有礙觀瞻的瑕疵。咱們應該想一種辦法讓這個方法與類掛鉤,讓它成爲類的一個屬性,不是全局的。因而,這就產生了「構造函數+原型法」的類構造方法。prototype
從上面的構造函數模式建立對象的例子上能夠看到,每建立一個對象實例,每一個對象中都有material()
這個成員方法,這樣看起來是否是會浪費內存空間,下降執行效率。因此JavaScript中提供了原型的方法能夠解決這個問題。code
原型:proto
在JavaScript中每一個函數都有一個原型屬性,即prototype
,當調用構造函數進行建立對象的時候,全部該構造函數原型的屬性在建立的對象上均可用。按照這樣的想法多個CreateCar均可以共享一個原型material
.htm
在構造函數+原型法中,咱們對於類的method期待獲得的效果是:
1. 僅是類的method而不是全局的。
2. 只在類被定義時建立一個method實例,而後被全部類的實例共用。
由這兩個目標,咱們很容易想到高級面嚮對象語言Java的private static變量的特色。JavaScript沒有爲咱們提供這麼簡單的符號來實現這個複雜功能,可是卻有一個屬性能夠幫咱們仿造出這種效果:prototype
。咱們先來看幾段prototype的使用代碼。對象
> function Car(){ } > > Car.prototype.material = "steel"; > > var car1 = new Car(); var car2 = new Car(); > > document.write(car1.material); //prints "steel" > document.write(car2.material); //prints "steel" > > //car1.prototype.material = "iron" //compile error:car1.prototype is > undefined car1.material = "iron"; > > document.write(car1.material); //prints "iron" > document.write(car2.material); //prints "steel" > document.write(Car.prototype.material); //prints "steel" > > Car.prototype.material = "wood"; var car3 = new Car(); > document.write(car1.material); //prints "iron" > document.write(car2.material ); //prints "wood" > document.write(car3.material ); //prints "wood" > document.write(Car.prototype.material); //prints "wood"
分析該段代碼前,須要明確兩個概念:對象的直屬屬性和繼承屬性。直接在構造函數中經過this.someproperty = xxx
這種形式定義的someproperty屬性叫作對象的直屬屬性,而經過如上第4行代碼那樣Car.prototype.material = "steel";
這種形式定義的material屬性叫作繼承屬性。由上面這段代碼,咱們能夠總結出prototype屬性的以下特色:blog
PS:對象實例在讀取某屬性時,若是在自己的直屬屬性中沒有查找到該屬性,那麼就會去查找function下的
prototype
的屬性。繼承
Tip:咱們能夠經過hasOwnProperty方法來判斷某屬性是直屬於對象仍是繼承自它的prototype屬性 car1.hasOwnProperty("material"); // true car2.hasOwnProperty("material"); // false "material" in car2;// true
構造函數+原型方式代碼以下
<script> function CreateCar(name, color) { this.name = name; this.color = color; } CreateCar.prototype.material = function() { //綁定material到函數原型鏈上,當實例化時這個函數會做爲每一個對象的構造函數 alert(this.name); } var car1 = new CreateCar('BMW', '白色'); var car2 = new CreateCar('Benz', '黑色'); console.log(car1); console.log(car2); console.log(car1.hasOwnProperty("material"));//false 由於material是原型鏈上的,不是對象自己的,因此false </script>
這個跟高級面嚮對象語言中的class的樣子更~加相似了吧?上述寫法只是在「語義」上達到了對類屬性和方法的封裝,不少面向對象思想的完美主義者但願在「視覺」上也達到封裝,所以就產生了「動態原型法」,請看下面的代碼:
function Car(color, title){ this.color = color; this.title = title; if (typeof Car._initialized == "undefined") { Car.prototype.start = function(){ alert("Bang!!!"); }; Car.prototype.material = "steel"; Car._initialized = true; } }
咱們看,其實Car.prototype
的屬性定義是能夠被放進Car function的定義之中的,這樣就達到了「視覺」封裝。可是咱們沒有單純的move,咱們須要加一個條件,讓這些賦值操做只執行一次,而不是每次建立對象實例的時候都執行,形成內存空間的浪費。添加_initialized
屬性的目的就在於此。
對於全部用字面量建立的對象而言,其prototype對象均爲Object.prototype(做爲一個特殊對象,Object.prototype沒有原型對象):
var x = {a:18, b:28}; console.log(x.__proto__);//Object {}
而對於全部用new操做符建立的對象而言,其prototype對象均爲constructor函數的prototype屬性:
var x = {a:18, b:28}; function Test(c){ this.c = c; } Test.prototype = x; var t = new Test(38); console.log(t);//Object {c=38, a=18, b=28} console.log(t.__proto__);//Object {a=18, b=28} console.log(t.__proto__.__proto__);//Object {}
總結出來一句話就是:用構造函數方式定義對象的全部非函數屬性,用原型方式定義對象的函數屬性。
整理於:
http://dbear.iteye.com/blog/613745
http://www.cnblogs.com/tomxu/archive/2012/02/21/2352994.html