1. 程式人生 > >建構函式、例項和原型的概念和關係

建構函式、例項和原型的概念和關係

每個函式都屬於物件,都會有一個屬性叫prototype。這個屬性指向一個物件,我們把他叫做當前函式的原型物件。原型物件下面有個屬性叫constructor.這個屬性指向當前函式。函式又分為普通函式和建構函式。這裡我們說一下建構函式。定義一個函式 :

function Person(x, y ) {this.age = x;this.name = y;
}var xiaoming= new Person(12, “xiaoming” );
這裡建立例項物件 xiaoming的時候就是呼叫了Person建構函式,使xiaoming有了自己的屬性和方法,之後xiaoming和Person也就沒有什麼直接交集了(可以理解為小明分手了,哎程式設計師好難╥..╥)但是每個例項物件都會有一個隱藏屬性[[prototype]],這個屬性在chrome/firefox下叫proto

,僅僅供學習除錯用.它指向的就是建構函式的原型物件。

原型物件的深入理解
對於這個原型物件,我們就要重點理解下了。這個物件的作用就是為了讓所有的例項物件都能共享這個物件的屬性和方法(當然例項本身的屬性和方法優先順序是高於原型的)。每個建構函式都會有一個預設的原型物件。我們只要在改原型物件上做文章就可以實現很多功能。

● 共享屬性和方法:

Person.prototype.eyes = 2;
Person.prototype.walk = function ( ){……};var xiaoming= new Person(12, “xiaoming” );var xiaohong= new Person(12, “xiaohong” );
xiaoming.eyes
xiaohong.eyes // 小明和小紅都有2隻眼xiaoming.walk
xiaohong.walk//小明和小紅都會走路
● 原型鏈:
我們先做一個假設,假如我們把一個函式物件Man的原型直接給換成另一個函式物件Person的例項物件xiaoming會怎麼樣呢?
前面說了,通過例項物件是可以找到函式物件Person的原型。那我們現在Man物件的例項xiaoming是不是也就可以訪問到Person物件的原型物件了呢。

function Man( ) {this.beard = “xxx”;
}
Man.prototype = new Person( 23, “xiaoming” );
這裡我們相當於把預設的那個原型給重寫了,給引數其實就是給原型新增屬性和方法var xiaoming = new Man();
xiaoming.beard //xxx 這裡例項xiaoming自己的屬性(小明有鬍子)xiaoming.age //23xiaoming.name //xiaoming 這兩個屬性是例項的原型上面的屬性(其實這個屬性是Person例項的屬性,但是現在的原型不就是Person例項嗎)xiaoming.eyes //2 這個屬性呢,是Person的原型物件上面的了
這裡我們基本上都可以訪問到,是不是有點繼承的味道了。如果我們再這樣搞一個物件,也這麼幹,這裡是不是就感覺像條鏈一樣。最頂端的物件是Object,也就是說到最後了。我們把這條連結方式叫做原型鏈。這也是繼承的依據。

繼承
和傳統的OOP語言來說,JavaScript語言比較蛋疼的是它沒有類這個機制。所以說我們事先js的繼承就從物件角度下手了。我們重點說一下依據原型鏈繼承的。(其他的繼承我就不說了,比如借用父物件的建構函式等,實用性不強)1.上面所說的實現原型鏈的方法雖然有點繼承的味道了,但是你有沒有發現 例項化xiaoming這個物件的時候呼叫了Man這個建構函式,但是xiaoming自己的age和name都沒能進行構造,只不過是原型上的屬性而已(其實是Person自己構造的,new Person( 23, “xiaoming” ))。我們其實可以這樣用call和apply這個object原型下面給我們定義好的方法改進下(call和apply方法自己看api說明吧)

function Man(x, y) {
Person.call(this, x, y); //這裡你可以這樣理解,this指的是Man,這樣其實就是借用Person建構函式this.beard = “xxx”;
}
我們把Man的建構函式這樣一改,例項化的時候傳參,這樣age和name這兩個屬性就是Man自己構造出來的了,並不會被共享

Man.prototype = new Person( );
Man.prototype.constructor = Man;var xiaoming = new Man(23, “xiaoming”);
這裡只是讓Man的原型的建構函式變成原有的建構函式,如果不加這一句的話,那麼Man原型的建構函式就變成undefied,因為例項和建構函式並沒有直接關係。這樣一來,原型找不到建構函式,這是非常蛋疼的事情,違反了原型鏈的定義啊。
這邊可能會有人問了,我為什麼不自己像鬍子beard 那個屬性一樣直接構造呢。
大哥,我這是舉例子,你以為實際的專案中就會有這麼兩個屬性嗎。而且這樣不正是繼承的目的嗎
可以少寫很多程式碼啊。(說多了都是淚)
但是也是有缺點的:兩次呼叫父類建構函式(第一次是在建立子類原型的時候,第二次是在子類建構函式內部);子類繼承父類的屬性,一組在子類例項上,一組在子類原型上(在子類原型上建立不必要的多餘的屬性,例項上的遮蔽原型上的同名屬性,是不是感覺有點多餘了 ,效率低。

2.為了改進這種方法,下面說的這種繼承方式是藉助我們偉大的道爺(這個人很厲害,自行百度)的靈感 。這種就是利用一個空函式物件來做一個橋樑.具體實現方式如下:

function inherits(Child, Parent) { var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
另外在子物件的建構函式中別忘了借用父物件的建構函式哦。(就是那個call或者apply方法)

這裡和上面的區別是,子物件的原型現在不是父物件的例項了,變成了空函式物件的例項(父物件不用再建立兩次了,並且子物件的原型上也不會有啥屬性和方法了)。而空函式物件的原型變成了父物件的原型。前面我們說過,有了例項就能找到原型。所以現在子物件原型和父物件原型是就建立關係了。這種方式現在是最穩的方法,也已經被很多框架給寫到原始碼裡面了。這裡我們就用google closure 關於繼承的兩個api,這邊簡單舉個例子:

Child = function( ){
goog.base(this);this.height = 12;
}
goog.inherits(Child, Parent);