爲了避免被罰200塊錢,不得不強迫本身在今天以前寫完這篇博客,人就是要對本身狠一點,想些招數來強迫本身,也許是件好事。前端
JS的原型鏈老是被端上前端面試桌上的一盤經典菜,不一樣的人從不一樣的角度去品鑑。今天我想從構造函數模式到原型模式再到原型鏈來闡述個人理解。面試
之前的我只是知道構造函數就是定義一個函數,函數名大寫,函數裏面給隱式返回的this對象添加屬性和方法,這個函數就是構造函數。chrome
至於爲何要用構造函數呢?就有些迷茫了,因此再次翻開了紅包書那些曾經使人頭疼的面向對象章節(不得不說JS高級程序設計真是一本好書)設計模式
首先由於面向對象的程序設計須要不少對象,不少類似又不一樣的對象,有對象纔好辦事嘛。瀏覽器
類似的地方抽離出來就成了抽象對象,好比你要找個身高多少、體重多少、長相如何......的異性。那每一個人的標準就不同,這些標準就成了對象的特殊性,而相同的就是都是某我的的異性朋友app
咱們試着來實現一下函數
function sexFriend(height, weight,appearance){
var o = new Object();
o.height=height;
o.weight=weight;
o.appearance=appearance;
o.saySth=function(){
alert('我願意')
}
return o
}
var friend1 = new sexFriend(169, 90, '沉魚落雁,閉月羞花');
var firend2 = new sexFriend(182, 150, '清秀俊朗,風度翩翩');
複製代碼
你看咱們能經過給這個函數傳入咱們指望的參數就能找到對的那我的吧。ui
這就是設計模式之——工廠模式,由於JS語言沒有其餘語言中的類的概念, 因此用函數來封裝以特定接口建立對象的細節。this
可是這種工廠模式僅僅解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題,即沒有辦法僅僅經過這些對象知道他們屬於什麼類型的對象,即是這些對象和構建他們的函數缺乏了關聯,就好比說有我的說上面建立的對象是豬的時候,你拿什麼證據去反駁他呢?spa
隨着JS的發展,構造函數模式應運而生 構造函數模式就可以解決工廠模式所帶來的問題,由於構造函數建立的對象與建立的函數之間創建了直接的聯繫。經過constructor
function SexFriend(height, weight, appearance){
this.height=height;
this.weight=weight;
this.appearance=appearance;
this.saySth=function(){
alert('我願意')
}
}
var friend1 = new SexFriend(168, 90, '傾國傾城');
var friend2 = new SexFriend(182, 150, '風流倜儻');
複製代碼
仔細比較上面的構造函數與工廠函數,他們有不少代碼都是類似的,如下是他們的不一樣:
friend1.constructor == SexFriend; // true
friend2.constructor == SexFriend; // true
複製代碼
這就使得建立的對象經過constructor屬性找到了標識他的對象類型
僅僅經過構造函數建立的對象實例是不夠的,每建立一個實例對象,屬性和方法都會隔離存在,沒法共用,從而致使資源的浪費。咱們來看一種解決方案
function SexFriend(height, weight, appearance){
this.height=height;
this.weight=weight;
this.appearance=appearance;
this.saySth=saySth;
}
function saySth=function(){
alert('我願意')
}
var friend1 = new SexFriend(168, 90, '傾國傾城');
var friend2 = new SexFriend(182, 150, '風流倜儻');
複製代碼
上面的栗子中,把saySth方法放到全局做用域就能實現共用,若是對象須要定義許多方法,那麼就要定義不少的全局函數,使得這些自定義的引用類型絲毫沒有封裝可言。
原型模式就是解決上面的問題的
咱們建立的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針指向一個對象(瞭解這個很重要)
咱們在瀏覽器打印上面的構造函數SexFriend的prototype出來看 它是這樣的
console.log(SexFriend.prototype);
/* { constructor: ƒ SexFirend(height, weight, appearance), __proto__: Object } */
複製代碼
這個對象的用途就是由其構造函數建立的全部對象實例均可以共用這個原型對象的屬性。 這個對象的constructor 屬性 就是指向的構造函數自己
SexFriend.prototype.constructor === SexFriend // true
複製代碼
想一想前面咱們說構造函數模式的時候,建立的每一個實例都有一個 constructor 屬性,事實上並非實例自己的屬性,而是共用了原型對象上的 constructor 屬性。
在chrome 瀏覽器去打印前面構造函數建立的實例對象frend1和friend2就會知道,這兩個對象除了擁有在構造函數內添加的屬性和方法以外,還有一個屬性 proto 這個屬性也是一個指針,就是指向原型對象的,所以這兩個實例對象都能訪問到共同的 constructor 屬性。
因此當咱們須要共用某些方法和屬性的時候就能夠利用原型模式將方法屬性綁定到原型對象上
來看具體實現:
function SexFriend(height, weight, appearance){
this.height=height;
this.weight=weight;
this.appearance=appearance;
// 將共用的方法綁定到原型對象裏
SexFriend.prototype.saySth=function(){
alert('我願意')
}
}
var friend1 = new SexFriend(168, 90, '傾國傾城');
var friend2 = new SexFriend(182, 150, '風流倜儻');
複製代碼
咱們在前面說到的原型對象本質上就是一個普通的對象,只不過這個對象與構造函數之間經過 constructor 屬性建立了聯繫。
假如咱們將構造函數的 prototype 的指針指向另一個構造函數的實例對象,以下:
function A(){
this.a = 1;
}
function B(){
this.b=2;
}
var b = new B();
A.prototype = b
var a = new A()
a.constructor === A // false
a.__proto__ === b // true
a.constructor === b.constructor // true
b.constructor=== b.__proto__.constructor; // true
b.__proto__.constructor=== B.prototype.constructor; // true
B.prototype.constructor=== B; // true
複製代碼
咱們把構造函數 A 的 prototype 屬性的指針指向了構造函數 B 的實例對象 b; 那麼再經過 A 建立的 a 的__proto__屬性指向的就是 b 了,即原型對象的指針指向了 b ,這時 a 與 A 之間的鏈接就斷了,可是 A 本來的Prototype(原型)對象仍然存在,只是指針再也不指向它了,
而且 a.constructor 已經指向了B
若是再將 B prototype 屬性指針指向構造函數 C 的實例 c , 那麼就有 a.constuctor === C了,由__proto__構成鏈接的經常的鏈子就是原型鏈了,使得 a.constuctor 找到最後的 C 。也使得實例對象 a 能沿着原型鏈繼承到全部鏈上的方法和屬性,從最近的原型到最遠的原型直到找到相應的屬性方法爲止。而最頂級的原型對象就是Object