javascript面向對象系列第三篇——實現繼承的3種形式

来源:http://www.cnblogs.com/xiaohuochai/archive/2016/08/13/5768176.html
-Advertisement-
Play Games

× 目錄 [1]原型繼承 [2]偽類繼承 [3]組合繼承 前面的話 學習如何創建對象是理解面向對象編程的第一步,第二步是理解繼承。本文是javascript面向對象系列第三篇——實現繼承的3種形式 【1】原型鏈 javascript使用原型鏈作為實現繼承的主要方法,實現的本質是重寫原型對象,代之以一 ...


×
目錄
[1]原型繼承 [2]偽類繼承 [3]組合繼承

前面的話

  學習如何創建對象是理解面向對象編程的第一步,第二步是理解繼承。本文是javascript面向對象系列第三篇——實現繼承的3種形式

 

【1】原型鏈

  javascript使用原型鏈作為實現繼承的主要方法,實現的本質是重寫原型對象,代之以一個新類型的實例

function Super(){
    this.value = true;
}
Super.prototype.getValue = function(){
    return this.value;
};
function Sub(){}
//Sub繼承了Super
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;

var instance = new Sub();
console.log(instance.getValue());//true

  原型鏈最主要的問題在於包含引用類型值的原型屬性會被所有實例共用,而這也正是為什麼要在構造函數中,而不是在原型對象中定義屬性的原因。在通過原型來實現繼承時,原型實際上會變成另一個類型的實例。於是,原先的實例屬性也就順理成章地變成了現在的原型屬性了

function Super(){
    this.colors = ['red','blue','green'];
}
function Sub(){};
//Sub繼承了Super
Sub.prototype = new Super();
var instance1 = new Sub();
instance1.colors.push('black');
console.log(instance1.colors);//'red,blue,green,black'
var instance2 = new Sub();
console.log(instance2.colors);//'red,blue,green,black'

【1.1】原型式繼承

  原型式繼承藉助原型可以基於已有的對象創建新對象,同時還不必因此創建自定義類型。從本質上講,object()對傳入其中的對象執行了一次淺複製

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

var superObj = {
    colors: ['red','blue','green']
};
var subObj1 = object(superObj);
subObj1.colors.push("black");

var subObj2 = object(superObj);
subObj2.colors.push("white");

console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]

  實際上,Object.create()方法規範化了原型式繼承

var superObj = {
    colors: ['red','blue','green']
};
var subObj1 = Object.create(superObj);
subObj1.colors.push("black");

var subObj2 = object(superObj);
subObj2.colors.push("white");

console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]

  [註意]原型式繼承雖然只是看上去將原型鏈繼承的一些程式性步驟包裹在函數里而已,與原型鏈繼承有著引用類型值的問題。但是,它們的一個重要區別是父類型的實例對象不再作為子類型的原型對象

  1、使用原型鏈繼承

function Super(){
    this.value = 1;
}
Super.prototype.value = 0;
function Sub(){};
//將父類型的實例對象作為子類型的原型對象
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;

//創建子類型的實例對象
var instance = new Sub;
console.log(instance.value);//1

  2、使用原型式繼承

function Super(){
    this.value = 1;
}
Super.prototype.value = 0;
function Sub(){};

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;

//創建子類型的實例對象
var instance = new Sub;
console.log(instance.value);//0

  上面的Object.create函數一行代碼Sub.prototype = Object.create(Super.prototype)可以分解為

function F(){};
F.prototype = Super.prototype;
Sub.prototype = new F();

  由上面代碼看出,子類的原型對象是臨時類F的實例對象,而臨時類F的原型對象又指向父類的原型對象;所以,實際上,子類可以繼承父類的原型上的屬性,但不可以繼承父類的實例上的屬性

【1.2】寄生式繼承

  寄生式繼承創建一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最後返回對象

function parasite(original){
    var clone = Object.create(original);//通過調用函數創建一個新對象
    clone.sayHi = function(){ //以某種方式來增強這個對象
        console.log("hi");
    };
    return clone;//返回這個對象
}
var superObj = {
    colors: ['red','blue','green']
};
var subObj1 = parasite(superObj);
subObj1.colors.push('black');
var subObj2 = parasite(superObj);
subObj2.colors.push('white');

console.log(superObj.colors);//["red", "blue", "green", "black", "white"]
console.log(subObj1.colors);//["red", "blue", "green", "black", "white"]

  [註意]寄生式繼承實際上只是原型式繼承的再包裝,與原型式繼承有著同樣的問題,且由於不能做到函數復用而降低了效率

 

【2】借用構造函數

  借用構造函數(constructor stealing)的技術(有時候也叫做偽類繼承或經典繼承)。基本思想相當簡單,即在子類型構造函數的內部調用超類型構造函數,通過使用apply()和call()方法在新創建的對象上執行構造函數

function Super(){
    this.colors = ['red','blue','green'];
}
function Sub(){
    //繼承了Super
    Super.call(this);
}
var instance1 = new Sub();
instance1.colors.push('black');
console.log(instance1.colors);// ['red','blue','green','black']
var instance2 = new Sub();
console.log(instance2.colors);// ['red','blue','green']

  相對於原型鏈而言,借用構造函數有一個很大的優勢,即可以在子類型構造函數中向超類型構造函數傳遞參數

function Super(name){
    this.name = name;
}
function Sub(){
    //繼承了Super,同時還傳遞了參數
    Super.call(this,"bai");
    //實例屬性
    this.age = 29;
}
var instance = new Sub();
console.log(instance.name);//"bai"
console.log(instance.age);//29  

  但是,如果僅僅是借用構造函數,那麼也將無法避免構造函數模式存在的問題——方法都在構造函數中定義,因此函數復用就無從談起了

 

【3】組合繼承

  組合繼承(combination inheritance)有時也叫偽經典繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性

function Super(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
Super.prototype.sayName = function(){
    console.log(this.name);
};
function Sub(name,age){
    //繼承屬性
    Super.call(this,name);
    this.age = age;
}
//繼承方法
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    console.log(this.age);
}
var instance1 = new Sub("bai",29);
instance1.colors.push("black");
console.log(instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
instance1.sayAge();//29

var instance2 = new Sub("hu",27);
console.log(instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"
instance2.sayAge();//27

  組合繼承有它自己的問題。那就是無論什麼情況下,都會調用兩次父類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。子類型最終會包含父類型對象的全部實例屬性,但不得不在調用子類型構造函數時重寫這些屬性

function Super(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
Super.prototype.sayName = function(){
    return this.name;
};
function Sub(name,age){
     // 第二次調用Super(),Sub.prototype又得到了name和colors兩個屬性,並對上次得到的屬性值進行了覆蓋
    Super.call(this,name);
    this.age = age;
}
//第一次調用Super(),Sub.prototype得到了name和colors兩個屬性
Sub.prototype = new Super(); 
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    return this.age;
};  

【3.1】寄生組合式繼承

  寄生組合式繼承與組合繼承相似,都是通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。只不過把原型繼承的形式變成了寄生式繼承。使用寄生組合式繼承可以不必為了指定子類型的原型而調用父類型的構造函數,從而寄生式繼承只繼承了父類型的原型屬性,而父類型的實例屬性是通過借用構造函數的方式來得到的

function parasite(original){
    var clone = Object.create(original);//通過調用函數創建一個新對象
    return clone;//返回這個對象
}

function Super(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
Super.prototype.sayName = function(){
    return this.name;
};
function Sub(name,age){
    Super.call(this,name);
    this.age = age;
}
Sub.prototype = parasite(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function(){
    return this.age;
}
var instance1 = new Sub("bai",29);
instance1.colors.push("black");
console.log(instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
instance1.sayAge();//29

var instance2 = new Sub("hu",27);
console.log(instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"
instance2.sayAge();//27

 

最後

  繼承這塊可能是ECMAScript中最難理解的部分。如果說作用域this機制的難在於繞,則這部分的難則在於混雜。每種模式都有自己的優點,而多個模式結合在一起就可能造成一些屬性的重置,這是最需要註意的地方

  更多的模式都是為了更好的解決問題。學習原理時學的深一點,解決問題時才能更順利點

  以上


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 例一 var x=10;全局變數(開闢空間)function outer(){x=20;//此處未聲明變數(未開闢空間),只給全局變數聲明瞭,此處賦值會把全局變數開闢的存儲空間的值替換掉(全局變數中的x替換成20)。function inner(){x=30;//此處未聲明變數(未開闢空間),只給全 ...
  • { "total": 16, "rows":[ { "name": "張三", "email": "[email protected]", "date": "2016-08-13" }, { "name": "李四", "email": "[email protected]", "date": " ...
  • web前端之HTML的大框架 body元素與frameset元素 對於從事html的人員來說,我們一般熟悉的框架是先聲明html ,然後在<html>標簽對里包著<head>標簽對和<body>標簽對,body元素定義文檔的主體,包含文檔的所有內容(比如文本、超鏈接、圖像、表格和列表等等)。而我們想 ...
  • 一、表單事件: 一、表單事件: input事件當<input>、<textarea>的值發生變化時觸發。此外,打開contenteditable屬性的元素,只要值發生變化,也會觸發input事件。input事件的一個特點,就是會連續觸發,比如用戶每次按下一次按鍵,就會觸發一次input事件。 inp ...
  • 事件是一種非同步編程的實現方式,本質上是程式各個組成部分之間的通信,DOM支持大量的事件; 本文通過這幾點向大家詳細解析事件處理的基本原理:事件類型、事件目標、事件處理程式、事件對象、事件傳播 最後再向大家介紹Event對象; (原創文章,轉摘請註明:蘇服:http://www.cnblogs.com ...
  • 雖然有很多插件可用,但為了共同提高,我做了一系列JavaScript實戰系列的實例,分享給大家,前輩們若有好的建議,請務必指出,免得誤人子弟啊! ( 原創文章,轉摘請註明:蘇服:http://www.cnblogs.com/susufufu/p/5768402.html ) 今天是第一戰:帶收放動畫 ...
  • 使用node-mysql,在nodejs中訪問mysql資料庫.包含連接池,sql轉義,多種查詢語句使用 ...
  • 首先給大家介紹javascript jquery中定義數組與操作的相關知識,具體內容如下所示: 1.認識數組 數組就是某類數據的集合,數據類型可以是整型、字元串、甚至是對象Javascript不支持多維數組,但是因為數組裡面可以包含對象(數組也是一個對象),所以數組可以通過相互嵌套實現類似多維數組的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...