JavaScript | 繼承

来源:http://www.cnblogs.com/hughdong/archive/2017/07/31/7264122.html
-Advertisement-
Play Games

(ฅ´ω`ฅ) 來源《JavaScript高級程式設計第三版》,建議學習時間 2小時 ...


—————————————————————————————————————————————————————————

繼承 - ECMAScript只支持實現繼承(依靠原型鏈),不支持介面繼承(函數沒有簽名)

原型鏈

  • 利用原型讓一個引用類型繼承另一個引用類型的屬性和方法,
  • 構造函數、原型、實例的關係:每個構造函數都有一個原型對象,原型對象包含一個指向構造函數的指針。實例包含一個指向原型對象的內部指針,在創建實例之後即指向原型對象
  • 而當A原型對象的指針指向B個原型對象時(此時A原型對象與B實例同級),就形成了一條原型鏈。
  • 圖解:

    原型搜索機制:當讀取模式訪問一個實例屬性時,首先會在實例中搜索該屬性,如果沒有找到該屬性則沿著原型鏈向上查找

    在例子<Demo-1>中,調用instance.getSuperValue(),先搜索實例instance,再搜索SubType.prototype,再搜索SuperType.protorype,最後一步才找到該方法。

    預設的原型:所有的引用類型預設都繼承了Object,所以預設原型的指針都會指向Object.prototype,完整的原型鏈如下:

    instance SubType.prototype SuperType.prototype Object.prototype

  • p.s.

    必須替換掉實例的原型後才能給實例添加方法

    不能使用對象字面量創建原型方法,這樣做會重寫原型鏈,如<Demo-3>

  • 缺點:

    包含引用類型值(Function Object Array)的原型屬性會被所有實例共用,在通過原型來實現繼承時,原型實際上會變成另一個類型的實例,所以原先的實例屬性就變成了現在的原型屬性了。<Demo-4>

    在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。

// "use strict";

// Demo - 1
// SuperType 擁有一個屬性和一個方法
// SubType 擁有一個屬性和一個方法,又從SuperType那裡繼承了一個屬性一個方法
function SuperType(){
    this.property = "111";
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.subproperty = "222";
}
// p.s.new操作之前,SubType.prototype指向的是function,不允許為function()定義.getSubValue方法,所以要將添加方法放在修改原型指向之後
// 操作之後SubType.prototype指向SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){ // 必須在SubType替換原型之後才能定義
    return this.subproperty;
}
var instance = new SubType();
console.log(instance.property); // 111
console.log(instance.getSuperValue()); // 111
console.log(instance.subproperty); // 222
console.log(instance.getSubValue()); // 222
console.log(instance.constructor); // f SuperType(){} 原本SubType中的constructor屬性被重寫
// 重寫SuperType.getSuperValue()
// 如果要重寫這個方法,會屏蔽原來的方法
// 換句話說,當通過SubType的實例調用getSuperValue時調用的就是這個重新定義的方法,但通過SuperType的實例調用時還會繼續調用原來的方法
var beforeReWrite = new SuperType();
SuperType.prototype.getSuperValue = function(){
    console.log("rewrite");
}
console.log(instance.getSuperValue()); // rewrite,this.property = undefined
console.log(SuperType.prototype.getSuperValue()); // rewrite,this.property = undefined
console.log(beforeReWrite.getSuperValue());

// Demo - 2
// 確認原型和實例的關係
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
// 另一種方法
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true

// Demo - 3
function SuperType2(){
    this.property = "1111";
}
SuperType2.prototype.getSuperValue = function(){
    return this.property;
}
function SubType2(){
    this.subproperty = "2222";
}
SubType2.prototype = new SuperType2();
SubType2.prototype = {
    getSubValue:function(){
        return this.subproperty;
    },
    someOtherMethod:function(){
        return false;
    }
}
var instance2 = new SubType2();
console.log(instance2 instanceof Object); // true
console.log(instance2 instanceof SuperType2); // false,原型鏈被切斷
console.log(instance2 instanceof SubType2); // true
// console.log(instance2.getSuperValue()); // error

// Demo - 4
function SuperType3(){
    this.colors = ["red","blue","green"];
}
function SubType3(){}
SubType3.prototype = new SuperType3();
var instance3 = new SubType3();
instance3.colors.push("black");
console.log(instance3.colors); // ["red", "blue", "green", "black"]
var instance4 = new SubType3();
console.log(instance4.colors); // ["red", "blue", "green", "black"]

 

借用構造函數(偽造對象 / 經典繼承)

  • 在子類型構造函數的內部調用超類型構造函數
  • 優點:

    解決了單獨使用原型鏈共用引用類型值屬性的問題

    可以在子類型構造函數中向超類型構造函數傳遞參數

  • 缺點:

    無法避免構造函數模式存在的問題:方法都在構造函數中定義,無法實現函數復用

// "use strict";

function SuperType(name) {
    this.name = name;
    this.colors = ["111", "222", "333"];
}

function SubType() {
    SuperType.call(this, "name1");
    this.age = 20;
}

var instance = new SubType();
instance.colors.push("444");
console.log(instance.colors); // ["111", "222", "333", "444"]
console.log(instance.name); // name1
console.log(instance.age); // 20
var instance2 = new SubType();
console.log(instance2.colors); // ["111", "222", "333"]

 

組合繼承(偽經典繼承)

  • 將原型鏈和借用構造函數組合,使用原型鏈實現對原型屬性和方法的繼承,通過借用構造函數來實現對實例屬性的繼承
  • 對應創建對象 <組合使用構造函數模式和原型模式>
  • 優點:最常用
  • 缺點:需要調用兩次超類型構造函數,一次在創建子函數原型時,另一次在子函數構造函數內部。調用子類型構造函數時需要重寫屬性
// "use strict";
function SuperType(name) {
    this.name = name;
    this.colors = ["111", "222", "333"];
}
SuperType.prototype.sayName = function() {
    console.log(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() {
    console.log(this.age);
}
var instance1 = new SubType("hugh", 20);
instance1.colors.push("444");
console.log(instance1.colors); // ["111", "222", "333", "444"]
instance1.sayName(); // hugh
instance1.sayAge(); // 20
var instance2 = new SubType("dong", 21);
console.log(instance2.colors); // ["111", "222", "333"]
instance2.sayName(); // dong
instance2.sayAge(); // 21

 

原型式繼承

  • 對應創建對象 <動態原型模式>
  • 沒有使用嚴格意義上的構造函數,藉助已有的對象創建新對象
  • 優點:

    在不想創建構造函數,只想讓一個對象與另一個對象保持類似的情況下,原型式繼承完全可以勝任

  • 缺點:

    包含引用類型值的屬性始終都會共用,就像原型模式一樣

// "use strict";
function object(o){
    function F(){} // 創建臨時性構造函數
    F.prototype = o; // 將傳入的對象作為構造函數的原型
    return new F(); // 返回臨時類型的一個新實例
}

var person = {
    name:"hugh",
    friends:["111",'222','333']
};

var anotherPerson = object(person);
anotherPerson.name = "dong";
anotherPerson.friends.push("444");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "hehe";
yetAnotherPerson.friends.push("555");

console.log(person.friends); // ["111", "222", "333", "444", "555"]
console.log(person.name); // hugh
console.log(anotherPerson.friends); // ["111", "222", "333", "444", "555"]
console.log(anotherPerson.name); // dong
console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555"]
console.log(yetAnotherPerson.name); // hehe

// 使用Object.create()規範化原型式繼承
// 以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性
var otherPerson1 = Object.create(person);
otherPerson1.friends.push("666");
console.log(yetAnotherPerson.friends); // ["111", "222", "333", "444", "555", "666"]
var otherPerson2 = Object.create(person,{
    name:{
        value:"test"
    }
});
console.log(otherPerson2.name);

 

寄生式繼承

  • 對應創建對象 <寄生構造函數 / 工廠模式>
  • 創建一個僅用於封裝繼承過程的函數,在內部增強對象,最後返回對象
  • 示範集成模式時使用的object()函數不是必須的,任何能夠返回新對象的函數都適用於此模式
  • 使用場景:在主要考慮對象而不是自定義類型和構造函數的情況下,寄生式繼承也是一種有用的模式
  • 缺點:無法做到函數復用,類似於構造函數模式
// "use strict";
function object(o) {
    function F() {} // 創建臨時性構造函數
    F.prototype = o; // 將傳入的對象作為構造函數的原型
    return new F(); // 返回臨時類型的一個新實例
}

function createAnother(original) { // 接收的函數作為新對象基礎的對象
    var clone = object(original);
    clone.sayHi = function() { // 添加新方法
        console.log('hi');
    };
    return clone;
}
var person = {
    name: "hugh",
    friends: ['111', '222', '333']
};
var person1 = createAnother(person);
person1.sayHi();
console.log(person1.name);
console.log(person1.friends);

 

寄生組合式繼承

  • 優點:

    最理想的繼承範式

    解決組合繼承重寫屬性的問題,只調用了一次SuperType構造函數

    避免了在SubType.prototype上創建不必要的屬性

    原型鏈保持不變

    能夠正常使用instanceofisPrototypeOf()

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

// 1.創建超類型原型的一個副本
// 2.為創建的副本添加constructor屬性,彌補因重寫原型而失去的屬性
// 3.將新創建的對象(即副本)賦值給子類型的原型
function inheritProtoType(subType,superType){
    var prototype = object(superType.prototype); // 創建對象
    prototype.constructor = subType; // 增強對象
    subType.prototype = prototype; // 指定對象
}
function SuperType(name){
    this.name = name;
    this.colors= [1,2,3,4];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
inheritProtoType(SubType,SuperType);
SubType.prototype.sayAge = function(){
    console.log(this.age);
}


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

-Advertisement-
Play Games
更多相關文章
  • 使用JS獲取地址欄參數 方法一: function GetQueryString(name) { var reg = new RegExp("(^|&)"+ name +"=([^&] )(&|$)"); var r = window.location.search.substr(1).match( ...
  • 微信小程式在無論在功能、文檔及相關支持方面,都是優於前面幾種微信賬號類型,它提供了很多原生程式才有的介面,使得我們的小程式在很多方面突破H5頁面應用的限制,更加接近原生程式的功能,因此微信小程式具有很大的前景想象力。它提供了自己的視圖層描述語言 WXML 和 WXSS,以及基於 JavaScript... ...
  • [1]引入 [2]基礎 [3]拒絕處理 [4]串聯 [5]響應多個 [6]繼承 [7]非同步 ...
  • 利用自定義函數編寫年月日時間表: (複雜寫法)如下: <body> <select id="year" size="1" style="width: 70px;"></select> <select id="month" size="1" style="width: 50px;"></select> ...
  • 實現效果:打開手機京東,可以看到首頁的頭部,以這個頭部為基礎,仿寫一個類似的樣式。 思路:仔細觀察可以發現,手機京東的頭部是以一個搜索欄和輪播特效組成的,而這個搜索欄是以輪播特效做為背景的,現在運用css3關鍵幀動畫,可以實現這樣的頭部效果。 測試:首先,寫一個簡單的測試來驗證思路是否正確,這樣可以 ...
  • 項目升級為react-router4後,就嘗試著根據官方文檔進行代碼分割。https://reacttraining.com/react-router/web/guides/code-splitting 在實際項目中,js,css文件預設通過webpack打包的話會很大,動不動就好幾兆。 在實際場景... ...
  • ...
  • 這個小程式旨在通過自然語言對話查詢快遞、身份證、天氣、詩歌、詞典等等的功能。 自然語言對話,即使用中文語言直接對程式下命令,比如:‘查一下天氣’,“幫我查一下123456這個運單號吧”,“我想聽李白的靜夜思”等等。 如果還是不明白,請閱讀博客 https://i.cnblogs.com/EditPo ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...