JS- 繼承

繼承

定義

子類可使用父類的全部功能,而且對這些功能進行擴展。繼承的過程,就是從通常到特殊的過程。javascript

繼承方式

接口繼承和實現繼承。
接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法;因爲函數沒有簽名,在 ECMAScript 中沒法實現接口繼承。ECMAScript 只支持實現繼承,並且其實現繼承主要是依靠原型鏈來實現的。php

原型鏈繼承(類時繼承)

功能:經過原型鏈實現對原型屬性和方法的繼承
基本思想:是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。
實現方式:使用的原型的方式,將方法添加在父類的原型上,而後子類的原型是父類的一個實例化對象
缺點:
1.在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。子類沒法定義本身的屬性,全部子類實例共享相同的屬性和方法
2.包含引用類型值的原型。包含引用類型值的原型屬性會被全部實例共享
2--示例以下:css

function SuperType(){
  this.colors = ["red", "blue", "green"];
  this.name = 'sss'; 
}

SuperType.prototype.getName = function () {
  console.log(this.name);
}

function SubType(){}
SubType.prototype = new SuperType();

var sub_1 = new SubType();
var sub_2 = new SubType();
console.log(sub_1 == sub_2) // false  數據類型不一樣

 var sub1 = new SubType();
 var sub2 = new SubType();
 console.log(sub1.colors == sub2.colors) // true
 sub1.name = 'ooo';
 sub1.colors = ["red", "blue"]  // -------1-------- 重寫sub1的colors屬性,切斷了sub1.colors本來棧內存指向堆內存的指針地址,在堆內存從新開闢了一塊區域

 sub1.getName(); // ooo
 console.log(sub1.colors) // ["red", "blue"]

 sub2.getName(); // sss
 console.log(sub2.colors) // ["red", "blue", "green"]
 console.log(sub1.colors == sub2.colors) // false
 
 
 var sub_3 = new SubType();
 var sub_4 = new SubType();
 console.log(sub_3.colors == sub_4.colors)  // true。 sub_3.colors、sub_4.colors在堆內存中是同一區域,都繼承自SubType.prototype.colors,即SuperType的實例屬性colors,因爲該屬性爲引用類型Array,Array原型上擁有push方法,因此根據原型鏈至關於在SubType.prototype.colors進行push操做,故而包含引用類型值的原型屬性會被全部實例共享
 sub_3.name = 'ooo';
 sub_3.colors.push('black'); //  -------2-------- xxx.push()中push方法是Array.prototype.push上的方法;因爲             sub_3.colors/sub_3/SubType.prototype/SuperType.prototype都無push方法。見上面的解析

 sub_3.getName(); // ooo
 console.log(sub_3.colors) // ["red", "blue", "green", "black"]


 sub_4.getName(); // sss
 console.log(sub_4.colors) // ["red", "blue", "green", "black"]
 console.log(sub_3.colors == sub_4.colors) // true

prototype && proto && constructor && 實例 關係
構造函數、原型和實例的關係:每一個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針html

關係圖:
java

代碼示例:chrome

function B() {}
var b = new B();

// console.log(b.__proto__.constructor === b.constructor === B.prototype.constructor === B);
console.log(b.__proto__ === B.prototype);
console.log(b.__proto__.constructor === B.prototype.constructor);
console.log(b.constructor === B); 
console.log(B.prototype.constructor === B);
// 都爲 true

解析:
prototype(原型)屬性:經過調用構造函數而建立的那個對象實例的原型對象
constructor 是在建立函數的時候額外添加的一個屬性,該屬性指向建立該實例的構造函數
每一個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。這個prototype(原型)屬性指向函數的原型對象B.prototype。在默認狀況下,全部原型對象都會自動得到一個 constructor (構造函數)屬性,這個屬性包含一個指向 prototype 屬性所在函數的指針。如:B.prototype.constructor 指向 B。
建立了自定義的構造函數以後,其原型對象默認只會取得 constructor 屬性;至於其餘方法,則都是從 Object 繼承而來的。當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部屬性)([[Prototype]]/proto),指向構造函數的原型對象app

prototype 每個函數對象都有一個顯式的prototype屬性(普通對象沒有prototype),它表明了對象的原型(Function.prototype是一個對象,有constructor和__proto__兩個屬性,constructor指向構造函數自己,__proto__指向於它所對應的原型對象)。
proto 每一個對象都有一個名爲__proto__的內部隱藏屬性,指向於它所對應的原型對象(chrome、firefox中名稱爲proto,而且能夠被訪問到)。原型鏈正是基於__proto__才得以造成(note:不是基於函數對象的屬性prototype)。函數

函數對象: 凡是經過New function()建立的對象
**全部對象都有__proto__屬性,只有函數對象擁有prototype**
xx.prototype是一個普通對象,包含constructor和__proto__屬性(constructor用來講明誰構造的實例,__proto__用來指向構造函數的prototype,繼承就是經過他來實現的)。
Object是一個函數對象,也是Function構造的,Object.prototype是一個普通對象
全部對象(包括普通對象,函數對象)的原型鏈(proto)最終都指向了Object.prototype,而Object.prototype.proto===null,原型鏈至此結束this

Function、Object:都是Js自帶的函數對象。
Object.constructor === Function
Function.constructor === Function
Function.__proto__ === Function.prototype
Object.__proto__ === Function.prototypefirefox

原型鏈模式:

// 聲明父類
function SuperType(){
   var privateVariable = 1; // 私有屬性
   this.property = true;
}
// 在原型上爲父類添加共有方法
SuperType.prototype.getSuperValue = function(){
    return this.property;
};

// 聲明子類
function SubType(){
    this.subproperty = false;
}
//繼承父類SuperType
SubType.prototype = new SuperType();

//爲子類添加共有方法
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue()); //true   

console.log(SuperType.prototype.constructor === SuperType) //true
console.log(instance.__proto__ === SubType.prototype) //true
console.log(SubType.prototype.__proto__ === SuperType.prototype) //true
// constructor 是在建立函數的時候額外添加的一個屬性,該屬性指向建立該實例的構造函數
console.log(SubType.prototype.constructor === SuperType) //true
console.log(instance.constructor === SuperType) //true

圖解:

借用構造函數繼承(對象冒充/僞造對象/經典繼承)

功能:借用構造函數實現對實例屬性的繼承
基本思想:在子類型構造函數的內部調用超類型構造函數。函數只不過是在特定環境中執行代碼的對象,所以經過使用 apply()和 call()方法也能夠在(未來)新建立的對象上執行構造函數。SuperType.call(this, param)。
優勢:能夠在子類型構造函數中向超類型構造函數傳遞參數。
缺點:函數沒法複用,各個實例化的子類互不影響,所以形成內存浪費。示例以下:

function SuperType1(){
   this.colors = ["red", "blue", "green"];
   this.name = 'sss'; 
 }

 var super_1 = new SuperType1();
 var super_2 = new SuperType1();
 console.log(super_1 == super_2); // false

 super_1.name = 'ooo';
 super_1.colors.push('black');
 console.log(super_1); // {name: 'ooo', colors: ["red", "blue", "green", "black"]}
 console.log(super_2); // {name: 'sss', colors: ["red", "blue", "green"]}

借用構造函數繼承模式:

// 聲明父類
function SuperType(name){
   this.name = name;
   this.colors = ["red", "blue", "green"];
} 
// 聲明子類
function SubType(){
   //繼承SuperType
   SuperType.call(this, 'ada'); // 直接改變this的指向,使經過this建立的屬性和方法在子類中複製一份
}
// 實例化
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.name);      // 'ada'
console.log(instance1.colors);    //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors);    //"red,blue,green"

組合繼承(僞經典繼承)

組合繼承 = 原型鏈繼承+借用構造函數繼承
基本思想:是使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。這樣,既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性。
組合模式:

function SuperType(name){
   this.name = name;
   this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
   alert(this.name);
};
function SubType(name, age){
   SuperType.call(this, name); //繼承屬性 
   this.age = age; // 自有屬性
}
//繼承方法
SubType.prototype = new SuperType(); 
SubType.prototype.constructor = SubType; 
SubType.prototype.sayAge = function(){
   alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green" 
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

原型式繼承

基本思想:在函數內部,先建立了一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例。從本質上講,在函數內部對傳入其中的對象執行了一次。
原型模式:

function object(o){
   function F(){}
   F.prototype = o;
   return new F();
}

實例:

var person = {
   name: "Nicholas",
   friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg"; // 重寫了anotherPerson的name屬性
anotherPerson.friends.push("Rob"); // 修改了原型鏈上的friends屬性

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda"; // 重寫了yetAnotherPerson的name屬性
yetAnotherPerson.friends.push("Barbie"); // 修改了原型鏈上的friends屬性

console.log(anotherPerson); // {'name': 'Greg'}
console.log(yetAnotherPerson); // {'name': 'Linda'}
console.log(anotherPerson.friends); // "Shelby,Court,Van,Rob,Barbie"
console.log(anotherPerson.hasOwnProperty('name'), anotherPerson.hasOwnProperty('friends')); // true false
console.log(person.name); "Nicholas"
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"

實例解析:
person.friends 不只屬於 person 全部,並且也會被 anotherPerson 以及 yetAnotherPerson 共享。至關於又建立了 person 對象的兩個副本。
此外,ECMAScript 5 經過新增 Object.create()方法規範化了原型式繼承。這個方法接收兩個參數:一 個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象。在傳入一個參數的狀況下, Object.create()與 object()方法的行爲相同。
Object.create()方法的第二個參數與 Object.defineProperties()方法的第二個參數格式相同:每一個屬性都是經過本身的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性。示例以下:

var person = {
   name: "Nicholas",
   friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
   name: {
     value: "Greg"
   }
});
alert(anotherPerson.name); //"Greg"

寄生式繼承

功能:主要使用寄生式繼承來爲對象添加函數和屬性
基本思路:寄生式繼承的思路與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後返回對象
缺點:函數沒法複用,各個實例化的子類互不影響,所以形成內存浪費。
寄生式模式:

function createAnother(original){ 
   var clone=object(original); //經過調用函數建立一個新對象
   clone.sayHi = function(){ //以某種方式來加強這個對象
     alert("hi");
   };
   return clone; //返回這個對象
}

// 調用
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

寄生組合式繼承

寄生組合式繼承 = 寄生式繼承 + 借用構造函數繼承。經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。
基本思想:在借用構造函數實現實例屬性繼承的基礎上,使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。
目的:爲了解決組合式繼承產生的兩次調用父類(超類型)構造函數
組合式繼承繼承:

function SuperType(name){
   this.name = name;
   this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
   alert(this.name);
};
function SubType(name, age){
   SuperType.call(this, name); // 第二次調用SuperType() 屏蔽(覆蓋/重寫)了原型中的兩個同名屬性name和colors
   this.age = age;
}
SubType.prototype = new SuperType(); // 第一次調用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
   alert(this.age);
};

寄生組合式繼承模式:

// 原型式繼承模式:就是類式繼承的封裝,實現的功能是返回一個實例,該實例的原型繼承了傳入的o對象
function object(o){
   function F(){}  //聲明一個過渡函數對象
   F.prototype = o; //過渡對象的原型繼承父對象
   return new F(); //返回一個過渡對象的實例,該實例的原型繼承了父對象
}

// 寄生組合式繼承模式 參數:子類型構造函數和超類型構造函數
function inheritPrototype(subType, superType){ 
   var prototype = object(superType.prototype); //建立對象 建立超類型原型的一個副本
   prototype.constructor = subType; //加強對象 爲建立的副本添加constructor屬性,從而彌補因重寫原型而失去的默認的constructor 屬性
   subType.prototype = prototype; //指定對象 將新建立的對象(即副本)賦值給子類型的原型
}
// 因爲在建立的對象prototype是超類型原型的一個副本時,prototype的原型被重寫,是superType.prototype;而每一個構造函數在建立是,構造函數的原型有一個默認屬性constructor,因此prototype的constructor屬性被修改成superType

// 示例:
function inheritPrototype(subClass, superClass) {
  //複製一份父類的原型保存在變量中,使得p的原型等於父類的原型
  var p = object(superClass.prototype); // 1    p 至關於 xxx.prototype
  // 等同於
  // function F() {}
  // F.prototype = superClass.prototype;
  // var p = new F();
          
  // console.log(p.__proto__ === F.prototype) // true 
  // console.log(p.__proto__ === superClass.prototype); // true
  console.log(p.constructor === superClass); // true
  
  //修正由於重寫子類原型致使子類原型的constructor屬性被修改,
  p.constructor = subClass; // 2
  
  console.log(p.constructor === subClass); // true
  
  //設置子類的原型
  subClass.prototype = p; // 3
  
  console.log(subClass.prototype);
  console.log(subClass.prototype.constructor === subClass); // true
}

//定義父類
var SuperClass = function (name) {
   this.name = name;
   this.books = ['javascript','html','css']
};
//定義父類原型方法
SuperClass.prototype.getBooks = function () {
   console.log(this.books)
};

//定義子類
var SubClass = function (name) {
   SuperClass.call(this,name)
}

// 調用inheritPrototype
inheritPrototype(SubClass,SuperClass);

var subclass1 = new SubClass('php')

圖解:

其餘

肯定原型和實例的關係

  1. instanceof 判斷實例與原型鏈中出現過的構造函數
    格式:instance instanceof 原型上的構造函數
  2. isPrototypeOf()  判斷實例與原型鏈所派生的實例的原型
    格式:構造函數.prototype.isPrototypeOf(instance)
    示例:
    // 根據上述代碼
    instance instanceof Object
    instance instanceof SuperType
    instance instanceof SubType

Object.prototype.isPrototypeOf(instance)
SuperType.prototype.isPrototypeOf(instance)
SubType.prototype.isPrototypeOf(instance)

Object.defineProperties() && Object.defineProperty()

Object.defineProperties()

功能:
方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,並返回這個對象。
若是不指定configurable, writable, enumerable ,則這些屬性默認值爲false,若是不指定value, get, set,則這些屬性默認值爲undefined

語法: Object.defineProperty(obj, prop, descriptor)
obj: 須要被操做的目標對象
prop: 目標對象須要定義或修改的屬性的名稱
descriptor: 將被定義或修改的屬性的描述符

代碼示例

var obj = new Object();

Object.defineProperty(obj, 'name', {
    configurable: false,
    writable: true,
    enumerable: true,
    value: '張三'
})

console.log(obj.name)  //張三

Object.defineProperties()

功能:方法直接在一個對象上定義一個或多個新的屬性或修改現有屬性,並返回該對象。
語法: Object.defineProperties(obj, props)
obj: 將要被添加屬性或修改屬性的對象
props: 該對象的一個或多個鍵值對定義了將要爲對象添加或修改的屬性的具體配置

代碼示例:

var obj = new Object();
Object.defineProperties(obj, {
    name: {
        value: '張三',
        configurable: false,
        writable: true,
        enumerable: true
    },
    age: {
        value: 18,
        configurable: true
    }
})

console.log(obj.name, obj.age) // 張三, 18

Object.getPrototypeOf()

ECMAScript 5增長了一個新方法Object.getPrototypeOf(),在全部支持的實現中,這個方法返回[Prototype]的值。

Object.getPrototypeOf(obj) == Object.prototype //true 
Object.getPrototypeOf(obj).name //"張三"

屬性與實例/原型的關係

hasOwnProperty()方法 能夠檢測給定屬性是不是該對象的自有屬性,即一個屬性是存在於實例中,仍是存在於原型中
格式:obj.hasOwnProperty(prop)
in 操做符
在單獨使用時,會在經過對象可以訪問給定屬性時返回 true,不管該屬性存在於實例中仍是原型中
格式:(prop in obj)
在使用 for-in 循環時,返回的是全部可以經過對象訪問的、可枚舉的(enumerated)屬性,其中既包括存在於實例中的屬性,也包括存在於原型中的屬性
格式:for(var prop in obj)

注:Object.keys()和 Object.getOwnPropertyNames()方法均可以用來替代 for-in 循環
Object.keys()獲取對象可枚舉屬性,Object.getOwnPropertyNames()獲取對象全部屬性

參考資源:js高級程序設計(紅寶書)

相關文章
相關標籤/搜索