JavaScript 原型與繼承

来源:https://www.cnblogs.com/pgjett/archive/2020/03/21/12537041.html
-Advertisement-
Play Games

JavaScript 中函數原型是實現繼承的基礎。prototype、construct、原型鏈以及基於原型鏈的繼承是面向對象的重要內容 ...


JavaScript 原型與繼承

JavaScript 中函數原型是實現繼承的基礎。prototype、construct、原型鏈以及基於原型鏈的繼承是面向對象的重要內容

prototype

  1. 原型即 prototype,是函數的一個屬性,是一個對象

    function Car() {}
    console.log(typeof Car.prototype);
    console.log(Car.prototype);
    // object
    // {...}
    
  2. 所有被構造函數構造出的對象都能訪問 prototype 上定義的屬性和方法

    function Car() {
    	this.brand = "BMW";
    	this.color = "red";
    }
    Car.prototype.price = "100 萬";
    var car1 = new Car();
    var car2 = new Car();
    console.log(car1.price);
    console.log(car2.price);
    // 100 萬
    // 100 萬
    
  3. 構造函數內部和 prototype 定義了同名屬性,實例對象會優先調用構造函數中的屬性

    function Car() {
    	this.price = "10 萬";
    }
    Car.prototype.price = "100 萬";
    var car1 = new Car();
    console.log(car1.price);
    // 10 萬
    
  4. 通過實例對象不能更改 prototype 上的屬性

    function Car() {}
    Car.prototype.price = "100 萬";
    var car1 = new Car();
    car1.price = "10 萬";
    console.log(Car.prototype.price);
    // 100 萬 
    

一般將不變化的內容或方法放在 prototype 下,需要動態變化的放在構造方法內,通過參數配置

constructor

  1. constructor 指向構造函數本身

    實例對象的 constructor 屬性指向構造函數

    function Car() {}
    var car = new Car();
    console.log(car.constructor);
    console.log(Car)
    // Car(){}
    // Car(){}
    
  2. constructor 可以被更改

    constructor 可以被修改,但是並不會影響實例化對象

    function Bike() {
    	this.name = "bike";
    }
    Bike.prototype.name = "Bike";
    function Car() {}
    Car.prototype = {
    	constructor: Bike
    }
    var car = new Car();
    console.log(Car.prototype);
    console.log(car.name);
    // {constructor: Bike(){}, ...} 
    // undefined
    

__proto__

  1. 構造函數在實例化時,將其 prototype 掛載到函數內 this 的 __proto__

    function Car() {}
    Car.prototype.name = "Jett";
    var car = new Car();
    console.log(Car.prototype);
    console.log(car.__proto__);
    // Car.prototype ->
    // {
    //	name: "Jett", 
    //	construct: Car(){}
    //	_proto_: {...}
    //	}
    // car._proto_ ->
    // {
    //	name: "Jett",
    //	construct: Car(){}
    //	_proto_: {...}			
    // }
    // 
    

    可以看到,列印出的 Car.prototype 和 car.__proto__ 內容一致。因為在實例化對象時,Car.prototype 被掛載到函數內的 this.__proto__ 上,即實例對象的 __proto__ 屬性上

    prototype 是構造函數的屬性,__proto__ 屬於每個實例對象的,是一個內部屬性,它們指向相同的內容

  2. 可以通過實例對象訪問 __proto__ 屬性,並對其進行修改

    function Car() {}
    Car.prototype.name = 'BWM';
    var car = new Car();
    console.log(car.name);
    car.__proto__= {
    	name:"Benz"
    }
    console.log(car.name);
    // BWM
    // Benz
    

    也可以更改 prototype 的屬性到達效果

    function Car() {}
    Car.prototype.name = 'BWM';
    var car = new Car();
    console.log(car.name);
    Car.prototype.name = 'Benz';
    console.log(car.name);
    // BWM
    // Benz
    

    但是,將 prototype 重新賦值並不能對之前實例化的對象造成影響

    function Car() {}
    Car.prototype.name = 'BWM';
    var car = new Car();
    console.log(car.name);
    Car.prototype = {
    	name: "Benz"
    }	
    console.log(car.name);
    // BWM
    // BWM
    

    這是因為重新賦值相當於創建新對象,使 prototype 指向的新的對象,而實例對象的 __proto__ 屬性依然指向原來的內容,相當於一個對象的兩個引用,其中一個不在指向該對象,而且指向了新對象

    這不能對已經實例化出的對象造成影響,但是後面再實例化對象則可以造成影響,因為實例化過程中將修改後的 prototype 掛載到了實例對象的 __proto__ 屬性下,二者指向同一對象

原型鏈

  1. prototype 中的 __proto__ 屬性

    function Car() {}
    var car = new Car();
    console.log(Car.prototype);
    

    當我們列印構造函數的 prototype 屬性時,可以看到

    {
    	constructor: Car(),
    	__proto__: {...}
    }
    

    prototype 中也有 __proto__ 屬性,實例化過程 protorype 被掛載到實例對象的 __proto__ 下,這就意味著實例對象的 __proto__ 中也有一個 __proto__ 屬性

    因為這裡的 prototype 是一個非空對象,是由 new Object() 或者其他自定義構造方法實例化出的,自然也有 __proto__ 屬性

  2. 鏈式的 __proto__

    原型鏈是由 __proto__ 組成的鏈接,原型鏈的頂端是 Object.prototype

    JuniorCoder.prototype.basicSkill = "html/css";
    function JuniorCoder() {
    	this.lowerSkill = "javascript"
    }
    var junior = new JuniorCoder();
    SeniorCoder.prototype = junior;
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    var senior = new SeniorCoder();
    console.log(senior);
    

    這裡將 JuniorCoder() 的實例對象賦值給 SeniorCoder.prototype,列印出

    SeniorCoder {
    	advcedSkill: "vue",
    	__proto__: { // senior.__proto__ ,即 SeniorCoder.protoype
    		lowerSkill: "javascript",
    		__proto__: { // junior.__proto__ ,即 JuniorCoder.prototype
    			basicSkill: "html/css",
    			__proto__: { // Object.prototype
    				constructor: Object(),
    				toString: toString()
    				// ...
    			}
    		}
    	}
    }
    

    可以看出,senior 的 __proto__ 屬性指向 JuniorCoder() 實例 junior,這是因為之前 將 junior 賦值給了 SeniorCoder.prototype

    此外,junior 的 __proto__ 也指向了一個對象,這個對象就是 JuniorCoder.porotype,相當於 new Object() 得出的,所以 junior 的 __proto__ 下的 __proto__ 就是 Object.prototype,這就是原型鏈的頂端,在裡面我們還可以看到 toString 方法等等

  3. 訪問原型鏈上屬性

    JuniorCoder.prototype.basicSkill = "html/css";
    JuniorCoder.prototype.sex = "man";
    function JuniorCoder() {
    	this.lowerSkill = "javascript"
    	this.age = 22;
    }
    var junior = new JuniorCoder();
    SeniorCoder.prototype = junior;
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    var senior = new SeniorCoder();
    console.log(senior.age);
    console.log(senior.sex);
    // 22
    // man
    

    senior 可以訪問 junior 本身的屬性,也可以訪問 JuniorCoder.prototype 上的屬性,因為 junior 被掛載到了 SeniorCoder.prototype 上

    JuniorCoder.prototype.basicSkill = "html/css";
    function JuniorCoder() {
    	this.lowerSkill = "javascript";
    	this.years = 3;
    }
    var junior = new JuniorCoder();
    SeniorCoder.prototype = junior;
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    var senior = new SeniorCoder();
    senior.years++;
    // 等同於 senior.years = senior.years + 1;
    console.log(senior.years);
    console.log(junior.years);
    // 4
    // 3
    

    可以看到,通過 senior 試圖改變 years 屬性並不能真正影響 junior 的 years 屬性,實際上只是在 senior 下創建了新的 years 屬性,並將 junior.years 加一的結果賦值給它

Object.creat()

  1. Object 的 creat 方法用於創建對象,參數指定 prototype,可以為對象或 null

    var test = {
    	name: "obj"
    }
    var obj = Object.create(test);
    console.log(obj.name);
    console.log(obj.__proto__ == test);
    // obj
    // true
    
  2. Object.creat(null)

    var obj = Object.create(null);
    console.log(obj);
    document.write(obj);
    // {}
    // 報錯
    

    控制台顯示 obj 是一個空對象,沒有任何屬性,包括 __proto__,如果使用 document.write(obj) 則會報錯,因為 document.write 方法會把參數轉成字元串再列印在頁面,預設調用 toString() 方法,toString 方法需要從原型鏈上繼承而來,而 obj 是一個完全的空對象,沒有原型鏈,也沒有 toString 方法,所以會報錯

基於原型的繼承

  1. 利用原型鏈實現繼承

    JuniorCoder.prototype.basicSkill = "html/css";
    function JuniorCoder() {
    	this.lowerSkill = "javascript"
    	this.age = 22;
    }
    var junior = new JuniorCoder();
    SeniorCoder.prototype = junior;
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    var senior = new SeniorCoder();
    

    senior 繼承了 junior 的自身屬性及原型鏈

  2. call/apply 實現繼承

    function JuniorCoder(lowerSkill) {
    	this.lowerSkill = lowerSkill;
    }
    function SeniorCoder(lowerSkill, advancedSkill) {
    	JuniorCoder.apply(this, [lowerSkill]);
    	this.advancedSkill = advancedSkill;
    }
    var senior = new SeniorCoder("javascript", "vue");
    

    繼承了 JuniorCoder 實例的自身屬性,不能繼承原型鏈

  3. 公共原型繼承

    JuniorCoder.prototype.basicSkill = "html/css";
    function JuniorCoder() {
    	this.lowerSkill = "javascript"
    }
    SeniorCoder.prototype = JuniorCoder.prototype;
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    var senior = new SeniorCoder();
    

    senior 繼承 JuniorCoder 實例的原型鏈,不繼承自身屬性,但是改動 SeniorCoder.prototype 會影響 JuniorCoder.prototype

  4. 中間對象繼承(聖杯模式)

    JuniorCoder.prototype.basicSkill = "html/css";
    function JuniorCoder() {
    	this.lowerSkill = "javascript"
    }
    Buffer.prototype = JuniorCoder.prototype;
    function Buffer() {}
    SeniorCoder.prototype = new Buffer();
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    SeniorCoder.prototype.basicSkill = "markdown";
    console.log(SeniorCoder.prototype.basicSkill);
    console.log(JuniorCoder.prototype.basicSkill);
    // markdown
    // html/css
    

    繼承原型鏈,不繼承自身屬性,prototype 不相互影響,這種繼承方式更為實用

    進行封裝以後,更適應企業級開發

    JuniorCoder.prototype.basicSkill = "html/css";
    
    function JuniorCoder() {
    	this.lowerSkill = "javascript"
    }
    
    function SeniorCoder() {
    	this.advancedSkill = "vue";
    }
    inherit(SeniorCoder, JuniorCoder);
    SeniorCoder.prototype.basicSkill = "markdown";
    console.log(new SeniorCoder());
    console.log(new JuniorCoder());
    
    function inherit(Target, Origin) {
    	Target.prototype = Object.create(Origin.prototype);
    	Target.prototype.constructor = Target;
    	Target.prototype.superClass = Origin;
    }
    

    使用 Object 的 creat 方法直接創建中間對象,將 construtor、superClass 屬性設置好,便於分析和維護

hasOwnProperty()

判斷屬性是否是實例對象本身的,如果是則返回 true

Car.prototype.brand = "BMW";
function Car() {
	this.color = "red";
}
var car = new Car();
console.log(car.hasOwnProperty("brand"));
console.log(car.hasOwnProperty("color"));
// false
// true

instanceOf

判斷實例對象的原型鏈上是否有某個構造方法

JuniorCoder.prototype.basicSkill = "html/css";

function JuniorCoder() {
	this.lowerSkill = "javascript"
}

function SeniorCoder() {
	this.advancedSkill = "vue";
}
inherit(SeniorCoder, JuniorCoder);

function inherit(Target, Origin) {
	Target.prototype = Object.create(Origin.prototype);
	Target.prototype.constructor = Target;
	Target.prototype.superClass = Origin;
}

var senior = new SeniorCoder();

console.log(senior instanceof SeniorCoder);
console.log(senior instanceof JuniorCoder);
console.log(senior instanceof Object);
// true
// true
// true

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

-Advertisement-
Play Games
更多相關文章
  • 1、數組扁平化(數組降維) 數組扁平化是指將一個多維數組變為一維數組 [1, [2, 3, [4, 5]]] > [1, 2, 3, 4, 5] 2、給定一個數組,將數組中的所有0移動到末尾,並保持非0元素的順序不改變。如 [0,1,0,3,12] 移動後的期望數組為 [1,3,12,0,0] 要求 ...
  • 經過昨天對移動端基礎的瞭解,今天就來用原生JS實現一下我們的幻燈片。 因為是用原生實現,所以本文篇幅較長,各位看官只需理解思路即可,代碼部分可以粗略看看。 畢竟我們有better-scroll這樣封裝好的框架能更快速實現效果。b( ̄▽ ̄)d 首先根據我們昨天的滑屏操作,先將幻燈片的滑屏效果做出來。這 ...
  • 排序演算法之堆排序 [Toc] 什麼是堆? + 堆是一顆完全二叉樹 + 堆分為 最大堆和最小堆 + 最大堆父節點都大於子節點, 最小堆父節點都小於子節點 + 左子節點: 2 i +1 (i: 父節點index) + 右子節點: 2 i+2 堆排序 利用最大堆實現升序, 最小堆實現降序. 因為最大堆的根 ...
  • JS排序演算法之快排和歸併 [Toc] 快速排序 原理: 選擇一個key(一般是第一個元素), 將數組劃分為兩個區域. 左邊全部區域小於等於key, 右邊全部大於key. 然後在通過這種方法將每個區域劃分為兩個區域. 整個過程可以遞歸實現,以此實現整個數據有序 + 時間複雜度: O(n log(n)) ...
  • 效果 貼上效果展示: 實現思路 樣式方面不多贅述,滾動區域是給固定高度,設置 來實現。 接下來看看js方面的實現,其實也很簡單,觸發的條件是: + = 。例子我會使用 來實現,和原生實現是一樣的。 可視高度(offsetHeight):通過 的 獲得,表示區域固定的高度。這裡我推薦通過 來獲取高度, ...
  • 為什麼要三次握手:① 防止失效的鏈接到達伺服器伺服器打開錯誤鏈接。② 客戶端如果長時間等待,會有一個超時重傳,但是之前那個被耽誤的請求還是會回到伺服器,伺服器會打開兩個連接。③ 有三次握手後客戶端會忽視滯留請求。 為什麼要四次揮手。 ① 當客戶端發送連接釋放報名後,伺服器端數據可能還沒有傳輸完。 為 ...
  • 淺拷貝 定義:直接將一個引用數據類型的地址,賦值給另一個變數存儲,兩個變數存儲的是相同的記憶體地址,在一個變數操作數據,另一個變數中的數據也會改變 案例: 總結: a給b賦值時是給的記憶體地址,兩個變數中的數據改變隨意一個變數的數據,另外一個變數中的數據也跟著改變,這種操作就被稱為淺拷貝 深拷貝 定義: ...
  • JavaScript進階之高階函數篇 簡介:歡迎大家來到woo爺說前端;今天給你們帶來的是JavaScript進階的知識,接下來的系列都是圍繞著JavaScript進階進行闡述;首先我們第一篇講的是高階函數。 高階函數定義:高階函數是指操作函數的函數;一般情況在項目開發過程中都會分兩種情況 函數可以 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...