ECMAScript只支持實現繼承,主要依靠原型鏈來實現。與實現繼承對應的是接口繼承,因爲script中函數沒有簽名,因此沒法實現接口繼承。瀏覽器
基本思想:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。
構造函數、原型和實例的關係:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個紙箱原型對象的內部指針。
基本用法:函數
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType() { this.subproperty = false; } // 子的原型是父的對象 SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; } var instance = new SubType(); console.log(instance.getSuperValue()); // true
關係圖:
但實際上,SuperType也有本身的原型,就是Object,這個原型是默認的。
全部函數的默認原型都是Object的實例,所以默認原型都會包含一個內部指針,指向Object.prototype。
因此完整的關係圖應該是
使用原型可以作到繼承,但實際中並不單獨使用原型鏈來實現繼承,緣由以下:
一、對於不須要‘父親’的私有屬性的繼承:咱們知道原型來建立對象,使得全部的實例都擁有這些共享的屬性和方法,咱們在使用原型鏈來繼承最主要的是SubType的原型變爲SuperType的實例對象,那麼原本是Super實例私有的屬性property,且處於SubType的原型中成爲SubType實例的共享屬性。
二、對於須要‘父親‘私有屬性的繼承:同一,咱們知道會繼承父親的私有屬性,但咱們沒法經過傳入參數到’父親‘的構造函數來實現屬性特有值的目的。
鑑於以上咱們開始使用第二種繼承方式。this
基本思想:在子類型構造函數的內部調用超類型構造函數spa
function SuperType() { this.colors = ['red', 'yellow', 'black']; } SuperType.prototype.getColor = function () { return this.colors; } function SubType() { // 繼承了SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push('pink'); // ['red', 'yellow', 'black','pink'] console.log(instance1.colors); var instance2 = new SubType(); console.log(instance2.colors); // ['red', 'yellow', 'black'] console.log(instance2 instanceof SuperType); // false console.log(instance2.getColor()); // instance2.getColor is not a function
此方法是在子類型中調用了超(父)類型的構造函數,使構造函數中的屬性初始化了。
繼承的是超類型中構造函數中的屬性,如上繼承了colors屬性,但沒有繼承SuperType原型中的getcolor方法。
使用此方法,咱們還能夠傳遞參數對屬性進行初始化prototype
function SuperType(age) { this.age=age; } function SubType() { // 繼承了SuperType SuperType.call(this,18); } var instance1 = new SubType(); console.log(instance1.age); // 18
若是須要確保SuperType構造函數不會重寫子類型的屬性,能夠在調用超類型構造函數後,再添加應該在子類型定義的屬性。
缺點:
一、超類型的原型不可見
二、全部屬性方法都必須寫在構造函數中,全部類型都只能使用構造函數模式建立指針
將原型鏈和借用構造函數的技術組合到一塊。
思想:使用原型鏈實現對原型屬性和方法的繼承,借用構造函數實現對實例屬性的繼承。code
function SuperType(age) { this.age = age; } SuperType.prototype.getAge = function () { return this.age; } function SubType(age) { // 繼承了SuperType SuperType.call(this, age); } SubType.prototype = new SuperType(20); var instance1 = new SubType(18); console.log(instance1.age); // 18 console.log(instance1.getAge()); // 18 console.log(instance1.__proto__.age); // 20 var instance2 = new SubType(17); instance2.__proto__.age=55; console.log(instance1.__proto__.age); // 55 console.log(instance2.__proto__.age); // 55
咱們能夠看到,實際上instance1和instance2的原型上仍然存在屬於SuperType的實例屬性的屬性。只是instance1和instance2有了各自的age屬性,不會再往原型上找。
instanceof和isPrototypeOf()也可以用於識別基於組合繼承建立的對象。
組合繼承避免了原型鏈和借用構造函數的缺陷並融合了二者的有點,成爲js中最經常使用的繼承模式。對象
思想:藉助原型能夠基於已有的對象建立新的對象,同時還沒必要所以建立自定義類型。blog
function object(o) { function F() { }; F.prototype = o; return new F(); } var person = { name: 'linda', friends: ['lily', 'shirley'] }; var antherPerson = object(person); antherPerson.friends.push('tom'); console.log(antherPerson.name); // linda console.log(antherPerson.friends); // ['lily', 'shirley', 'tom]
這個方法和原型方法原理同樣,只不過把子類型的原型設置成超類型的實例對象包含在方法object中。
ECMAScript5中新增了object.create()方法來規範原型式繼承(做用與上述object函數做用相似),第一個參數是想要繼承的超類型的實例對象,第二個參數是子類型所具備的屬性。繼承
var person={ name:'lily', age:12 } var anotherPerson=Object.create(person,{name:{value:'linda'}}); console.log(anotherPerson.name); // 'linda'
第二個參數的寫法必須如上的格式。
支持Object.create()方法的瀏覽器有ie9+,Firefox4.+、Safari5+、Opera12+和Chrome
思想:與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真的是它作了全部工做同樣返回對象。
代碼:
function object(o){ function F(){} F.prototype=o; return new F(); } var person = { name: 'lily', age: 12 } function createAnotherPerson(original){ var clone=object(original); clone.sayHi=function(){ console.log('hi'); } return clone; } var anotherPerson =createAnotherPerson(person); anotherPerson.sayHi(); // 'hi' console.log(anotherPerson.name); // 'linda'
組合繼承是js最經常使用的繼承模式,能夠結合不一樣的模式的優勢,但組合繼承,每次都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是在子類型構造函數內部。
上述形成的結果是子類型實例中有兩組超類型的構造函數中定義的屬性,一組在子類型的實例中,一組在子類型實例的原型中。寄生組合式繼承能夠解決上述缺點。
function Super(name) { this.name = name; } Super.prototype.sayName = function () { console.log(this.name); } function Sub(age) { Super.call(this, 'linda'); this.age = age; } Sub.prototype = new Super(); Sub.constructor = Sub; var type=new Sub(); type.sayName(); // 'linda' console.log(type.name); // 'linda' console.log(type.__proto__.name); // undefined
思想:借用構造函數來繼承屬性,借用原型鏈來繼承方法。即繼承超類型的原型,而後再將結果指定給子類型的原型。
封裝一下即:
function Super(name) { this.name = name; } Super.prototype.sayName = function () { console.log(this.name); } function Sub(age) { Super.call(this, 'linda'); this.age = age; } function object(o) { function F() { } F.prototype = o; return new F(); } function inheritPrototype(subType, superType) { var prototype = object(superType.prototype); // 建立對象 prototype.constructor = subType; // 加強對象 subType.prototype = prototype; // 指定對象 } inheritPrototype(Sub, Super); var type = new Sub(); type.sayName(); // 'linda' console.log(type.name); // 'linda' console.log(type.__proto__.name); // undefined
這個模式的優勢體如今
一、只調用了一次Super構造函數,高效率
二、避免了在Sub.prototype上面建立沒必要要的多餘的屬性
三、原型鏈保持不變
開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式