從設(shè)計(jì)模式的角度講,原型模式是用于創(chuàng)建對(duì)象的一種模式,如果我們想要?jiǎng)?chuàng)建一個(gè)對(duì)象,一種方法是先指定它的類型,然后通過類來創(chuàng)建這個(gè)對(duì)象.原型模式選擇了另外一種方式,我們不在關(guān)系對(duì)象的具體類型,而是找到一個(gè)對(duì)象,然后通過克隆創(chuàng)建一個(gè)一模一樣的對(duì)象.
原型模式的實(shí)現(xiàn)關(guān)鍵,是語言本身是否提供了clone方法.ECMAScript5提供了Object.create方法,可以用來克隆對(duì)象.
var Plane = function(){
this.blood = 100;
this.attackLevel = 1;
this.defenseLevel = 1;
};
var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;
var clonePlane = Object.create(plane);
console.log(clonePlane);
//在不支持Object.create方法的瀏覽器中,可以使用:
Object.create = Object.create||function(obj){
var F=function(){};
F.prototype = obj;
return new F();
}
原型模式的真正目的并非在于需要得到一個(gè)一模一樣的對(duì)象,而是提供了一種便捷的方式去創(chuàng)建某個(gè)類型的對(duì)象,克隆只是創(chuàng)建這個(gè)對(duì)象的過程和手段。
javascrpit本身是一門基于原型的面向?qū)ο蟮恼Z言,它的對(duì)象系統(tǒng)就是使用原型模式來搭建的,在這里稱之為原型編程范型也許更合適。
JavaScript中的原型繼承 原型編程范型至少要包括以下基本規(guī)則:
1.所有的數(shù)據(jù)都是對(duì)象
- 所有的數(shù)據(jù)都是對(duì)象
- 要得到一個(gè)對(duì)象,不是通過實(shí)例化類,而是找到一個(gè)對(duì)象作為原型并克隆它。
3.對(duì)象會(huì)記住它的原型。
4.如果對(duì)象無法響應(yīng)某個(gè)請(qǐng)求,它會(huì)把這個(gè)請(qǐng)求委托給它自己的原型。
試試上,JavaScript中的根對(duì)象是object.prototype對(duì)象。object.prototype對(duì)象是一個(gè)空的對(duì)象。我們?cè)贘avaScript遇到的每個(gè)對(duì)象,實(shí)際上都是從object.prototype對(duì)象克隆而來的,object.prototype對(duì)象就是它們的原型。
可以利用ECMAScript5提供的Object.getPrototypeOf來查看這兩個(gè)對(duì)象的原型:
var obj1 = new Object();//var obj1 = {};
console.log(Object.getPrototypeOf(obj1)===Object.prototype);//輸出:true
2.要得到一個(gè)對(duì)象,不是通過實(shí)例化類,而是找到一個(gè)對(duì)象作為原型并克隆它
在JavaScript語言里,我們并不需要關(guān)心克隆的細(xì)節(jié),因?yàn)檫@是引擎內(nèi)部負(fù)責(zé)實(shí)現(xiàn)的。我們所需要做的只是顯式地調(diào)用var obj1 = new object()或者var obj1 = {}。此時(shí),引擎內(nèi)部會(huì)從object.prototype上面克隆一個(gè)對(duì)象出來,我們最終得到的就是這個(gè)對(duì)象。
3.對(duì)象會(huì)記住它的原型
如果請(qǐng)求可以在一條鏈條中依次往后傳遞,那么每個(gè)節(jié)點(diǎn)都必須知道它的下一個(gè)節(jié)點(diǎn)。JavaScript語言中的原型鏈查找機(jī)制,每個(gè)對(duì)象至少應(yīng)該記住它自己的原型。
JavaScrpit的真正來說,其實(shí)并不能說對(duì)象有原型,而只能說對(duì)象的構(gòu)造器的原型。
JavaScript給對(duì)象提供了一個(gè)名proto的隱藏屬性,某個(gè)對(duì)象proto屬性默認(rèn)會(huì)指向它的構(gòu)造器的原型對(duì)象,即{Constructor}.prototype。實(shí)際上,proto就是對(duì)象跟“對(duì)象構(gòu)造器的原型”聯(lián)系起來的紐帶。
4. 如果對(duì)象無法響應(yīng)某個(gè)請(qǐng)求,它會(huì)把這個(gè)請(qǐng)求委托給它的構(gòu)造器的原型
JavaScript的對(duì)象最初都是由Object.prototype對(duì)象克隆而來的,但對(duì)象構(gòu)造器的原型并不僅限于Object.prototype上,而是可以動(dòng)態(tài)指向其他對(duì)象.這樣一來,當(dāng)對(duì)象a需要借用對(duì)象b的能力時(shí),可以有選擇性的把對(duì)象a的構(gòu)造器的原型指向?qū)ο骲,從而達(dá)到繼承的效果.
var obj = {name:'sven'};
var A = function(){};
A.prototype = obj;
var a = new A();
console.log(a.name); //輸出:sven
我們來看看執(zhí)行這段代碼的時(shí)候,引擎做了哪些事情:
- 首先, 嘗試遍歷對(duì)象a中的所有屬性,但沒有找到name這個(gè)屬性.
- 查找name屬性的這個(gè)請(qǐng)求被委托給對(duì)象a的構(gòu)造器的原型,它被a.proto記錄著并且指向A.prototype,而A.prototype被設(shè)置為對(duì)象obj.
- 在對(duì)象obj中找到了name屬性,并返回它的值.
當(dāng)我們期望得到一個(gè)"類"繼承自另外一個(gè)"類"的效果時(shí):
- 首先,嘗試遍歷對(duì)象b中的所有屬性,但沒有找到name這個(gè)屬性.
- 查找name屬性的請(qǐng)求被委托給對(duì)象b的構(gòu)造器的原型,它被b.proto記錄著并指向B.prototype,而B.prototype被設(shè)置為一個(gè)通過new A()創(chuàng)建出來的對(duì)象.
- 該對(duì)象中依然沒有找到name屬性,于是請(qǐng)求被繼續(xù)委托給這個(gè)對(duì)象構(gòu)造器的原型A.prototype.
- 在A.prototype中找到了name屬性,并返回它的值.
在當(dāng)前的JavaScript引擎下,通過Object.create來創(chuàng)建對(duì)象的效率并不高,通常比通過構(gòu)造函數(shù)創(chuàng)建對(duì)象要慢.另外,通過設(shè)置構(gòu)造器的prototype來實(shí)現(xiàn)原型繼續(xù)的時(shí)候,除了根對(duì)象Object.prototype本身之外,任何對(duì)象都會(huì)有一個(gè)原型.而通過Object.create(null)可以創(chuàng)建出沒有原型的對(duì)象.