原型模式是js對繼承的一種實現javascript
prototype:構造函數中的屬性,指向該構造函數的原型對象。java
constructor:原型對象中的屬性,指向該原型對象的構造函數函數
_proto_:實例中的屬性,指向new這個實例的構造函數的原型對象this
例子:spa
//Person構造函數 function Person() { name = 'Person'; this.height = '160cm'; } //在其原型對象中添加age屬性 Person.prototype.age = '18'; //Person的實例p1 var p1 = new Person(); p1.age;//18 p1.name;//undefined p1.height;//160cm
上圖中原型與構造函數與實例的關係以下:prototype
** var p1 = new Person()都發生了什麼?code
1.var p1 = new Object(); //此時p1._proto_ = Object Prototype對象
2.p1._proto_ = Person.prototype;繼承
3.Person.call(p1);//使用新對象p1調用函數Person,將this做用域給p1ip
在定義構造函數的prototype屬性的時候,直接吧一個對象賦值給prototype。
例子:
//Person構造函數 function Person(){ name = 'Person'; this.height = '160cm'; } //原型爲一個Object實例,並有age屬性 Person.prototype = { age: '18' } //Person的實例p1 var p1 = new Person(); p1.age;//18 p1.name;//undefined p1.height;//160cm
上圖中原型與構造函數與實例的關係以下:
在實例p1中想要調用一個方法或者屬性的時候會沿原型鏈向上查找。
原型鏈存在的問題,以下:
//父類構造函數 function Super(){ this.color = ['red','black','blue']; } //子類構造函數 function Sub(){} //子類繼承父類 Sub.prototype = new Super(); //新建一個子類的實例 var ins1 = new Sub(); ins1.color.push('green'); var ins2 = new Sub(); //由於color爲一個引用對象,ins1和ins2的color都指向同一個地址,修改一個就會修改全部實例的color ins2.color;//'red,belck,blue,green'
防止在原型鏈模式中,全部子類的實例公用一個父類構造函數的引用對象。
使用 借用構造函數 的方式實現繼承。在子類構造函數的內部,調用超類的構造函數構造。以下:
//父類構造函數 function Super(){ this.color = ['red','black','blue']; } //子類構造函數 function Sub(){ //調用父類構造函數實現繼承 Super.call(this); } //新建一個子類的實例 var ins1 = new Sub(); ins1.color.push('green'); ins1.color;//'red,belck,blue,green' //在實例ins2上從新執行Super構造函數,從新初始化對象,ins2擁有本身的color屬性 var ins2 = new Sub(); ins2.color;//'red,belck,blue'
結合 原型鏈+借用構造函數 方式,實現組合繼承。
借用構造函數使每一個實例擁有本身的屬性;原型鏈使每一個實例能夠共用方法,實現方法的複用。
//Super中定義屬性name function Super(name){ this.name = name; this.color = ['red','green']; } //Super的原型中定義方法 Super.prototype.sayname = function(){ console.log(this.name); } function Sub(name, age){ //經過構造函數的方式繼承Super的屬性 Super.call(this, name); //定義本身的屬性 this.age = age; } //經過原型鏈的方式繼承方法 Sub.prototype = new Super(); Sub.prototype.constructor = Sub; var ins1 = new Sub('ins1',18);
缺點:會調用兩次超類的構造函數,一次在Super.call(this, name); 一次在Sub.prototype = new Super();
致使Sub原型上有屬性name、age,Sub實例上也有屬性name、age。
藉助原型,基於已有的對象建立新對象。
object.Create(參數1,參數2); 參數1用做新對象的原型對象,參數2爲新對象定義額外屬性的對象。
傳入一個參數的狀況:
var person = { name: 'person' } //基於已有的person對象,建立一個新的anthorp對象 var anthorp = Object.create(person); //至關於以下語句 function object(o){ function F(){}; F.prototype = o; return new F(); } //獲得一個以person爲原型的構造函數的實例 var anthorp = object(person);
傳入兩個參數的狀況:
var person = { name: 'person' } //基於已有的person對象,建立一個新的anthorp對象,覆蓋以前的name,並新增age var anthorp = Object.create(person,{ name: { value: 'anthorp' }, age: { value: 18 } }); anthorp.name; //anthorp
建立一個用於封裝繼承過程的函數,在函數內部加強對象。並返回對象。
talk is cheap, show me code。
//建立一個用於封裝繼承過程的函數,傳入obj function create(obj){ //經過調用object方法以obj爲基礎建立一個新對象。此處object表示任何可以返回新對象的函數 var clone = object(obj); //覺得對象新增方法的方式加強對象 clone.sayHi = function(){ console.log('hi'); }; //返回對象 return clone; } //使用create函數 //一個基礎對象person var person = { name: 'person' }; //基於基礎對象使用寄生模式生成的新對象 var anthorp = create(person); anthorp.sayHi();
上面講到組合繼承的缺點:會調用兩次超類的構造函數。
寄生組合式繼承:借用構造函數來繼承屬性,經過原型鏈混成來繼承方法。沒必要在子類原型中調用超類的構造函數。使用寄生繼承來繼承超類的原型,再將結果制定給子類的原型。
//寄生組合式繼承 function inheritProto(Sub, Super){ //根據Super的原型建立一個新的對象proto var proto = object(Super.prototype); //加強新對象,爲其賦construtor值 proto.constructor = Sub; //將新對象賦值給子類的原型。 Sub.prototype = proto; } //使用 //Super中定義屬性name function Super(name){ this.name = name; this.color = ['red','green']; } //Super的原型中定義方法 Super.prototype.sayname = function(){ console.log(this.name); } function Sub(name, age){ //經過構造函數的方式繼承Super的屬性,只在此處調用一次Super構造函數 Super.call(this, name); //定義本身的屬性 this.age = age; } //調用函數,實現繼承。代替以前的Sub.prototype = new Super();語句,防止Super構造函數調用兩次 inheritProto(Sub,Super);