JavaScript原型與繼承的秘密

来源:https://www.cnblogs.com/shipengfei/archive/2019/01/17/10283114.html
-Advertisement-
Play Games

在GitHub上看到的關於JavaScript原型與繼承的講解,感覺很有用,為方便以後閱讀,copy到自己的隨筆中。 原文地址:https://github.com/dreamapplehappy/blog/blob/master/2018/12/30/README.md 首先我們需要知道的是,Ja... ...


在GitHub上看到的關於JavaScript原型與繼承的講解,感覺很有用,為方便以後閱讀,copy到自己的隨筆中。

原文地址:https://github.com/dreamapplehappy/blog/blob/master/2018/12/30/README.md

首先我們需要知道的是,JavaScript是一種動態語言,本質上說它是沒有Class(類)的;但是它也需要一種繼承的方式, 那就是原型繼承;JavaScript對象的一些屬性和方法都是繼承自別的對象。

很多同學對JavaScript的原型和繼承不是很理解,一個重要的原因就是大家沒有理解__proto__prototype這兩個屬性的意思。 接下來我們先來好好梳理一下這兩個屬性,看看它們存在哪裡,代表了什麼意義,又有什麼作用。

首先來說一下__proto__這個屬性吧,我們需要知道的是,除了nullundefined,JavaScript中的所有數據類型都有這個屬性; 它表示的意義是:當我們訪問一個對象的某個屬性的時候,如果這個對象自身不存在這個屬性, 那麼就從這個對象的__proto__(為了方便下麵描述,這裡暫且把這個屬性稱作p0)屬性上面 繼續查找這個屬性,如果p0上面還存在__proto__(p1)屬性的話,那麼就會繼續在p1上面查找響應的屬性, 直到查找到這個屬性,或者沒有__proto__屬性為止。

我們可以用下麵這兩幅圖來表示:

1

上面這幅圖表示在obj原型鏈上面找到了屬性名字是a的值

2

上面這幅圖表示在obj原型鏈上面沒有找到屬性名字是a的值

我們把一個對象的__proto__屬性所指向的對象,叫做這個對象的原型;我們可以修改一個對象的原型來讓這個對象擁有某種屬性,或者某個方法。

// 修改一個Number類型的值的原型
const num = 1;
num.__proto__.name = "My name is 1";
console.log(num.name); // My name is 1

// 修改一個對象的原型
const obj = {};
obj.__proto__.name = "dreamapple";
console.log(obj.name); // dreamapple

這裡需要特別註意的是,__proto__這個屬性雖然被大多數的瀏覽器支持,但是其實它僅在ECMAScript 2015 規範中被準確的定義, 目的是為了給這個傳統的功能定製一個標準,以確保瀏覽器之間的相容性。通過使用__proto__屬性來修改一個對象的原型是非常慢且影響性能的一種操作。 所以,現在如果我們想要獲取一個對象的原型,推薦使用Object.getPrototypeOf 或者Reflect.getPrototypeOf,設置一個對象的原型推薦使用Object.setPrototypeOf或者是Reflect.setPrototypeOf

到這裡為止,我們來對__proto__屬性做一個總結:

  • 存在哪裡? 除了nullundefined所有其他的JavaScript對象或者原始類型都有這個屬性
  • 代表了什麼? 表示了一個對象的原型
  • 有什麼作用? 可以獲取和修改一個對象的原型

說完__proto__屬性,接下來我們就要好好的來理解一下prototype屬性了;首先我們需要記住的是,這個屬性一般只存在於函數對象上面; 只要是能夠作為構造器的函數,他們都包含這個屬性。也就是說,只要這個函數能夠通過使用new操作符來生成一個新的對象, 那麼這個函數肯定具有prototype屬性。因為我們自定義的函數都可以通過new操作符生成一個對象,所以我們自定義的函數都有prototype 這個屬性。

// 函數字面量
console.log((function(){}).prototype); // {constructor: ƒ}

// Date構造器
console.log(Date.prototype); // {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}

// Math.abs 不是構造器,不能通過new操作符生成一個新的對象,所以不含有prototype屬性
console.log(Math.abs.prototype); // undefined

那這個prototype屬性有什麼作用呢?這個prototype屬性的作用就是:函數通過使用new操作符生成的一個對象, 這個對象的原型(也就是__proto__)指向該函數的prototype屬性。 那麼一個比較簡潔的表示__proto__prototype 屬性之間關係的等式也就出來了,如下所示:

// 其中F表示一個自定義的函數或者是含有prototype屬性的內置函數
new F().__proto__ === F.prototype // true
我們可以使用下麵這張圖來更加形象的表示上面這種關係:3

看到上面等式,我想大家對於__proto__prototype之間關係的理解應該會更深一層了。

好,接下來我們對prototype屬性也做一個總結:

  • 存在哪裡? 自定義的函數,或者能夠通過new操作符生成一個對象的內置函數
  • 代表了什麼? 它表示了某個函數通過new操作符生成的對象的原型
  • 有什麼作用? 可以讓一個函數通過new操作符生成的許多對象共用一些方法和屬性

其實到這裡為止,關於JavaScript的原型和繼承已經講得差不多了;下麵的內容是一些基於上面的一些拓展, 可以讓你更好地理解我們上面所說的。

當我們理解了上面的知識點之後,我們就可以對下麵的表達式做一個判斷了:

// 因為Object是一個函數,函數的構造器都是Function
Object.__proto__ === Function.prototype // true

// 通過函數字面量定義的函數的__proto__屬性都指向Function.prototype
(function(){}).__proto__ === Function.prototype // true

// 通過對象字面量定義的對象的__proto__屬性都是指向Object.prototype
({}).__proto__ === Object.prototype // true

// Object函數的原型的__proto__屬性指向null
Object.prototype.__proto__ === null // true

// 因為Function本身也是一個函數,所以Function函數的__proto__屬性指向它自身的prototype
Function.__proto__ === Function.prototype // true

// 因為Function的prototype是一個對象,所以Function.prototype的__proto__屬性指向Object.prototype
Function.prototype.__proto__ === Object.prototype // true

如果你能夠把上面的表達式都梳理清楚的話,那麼說明你對這部分知識掌握的還是不錯的。

談及JavaScript的原型和繼承,那麼我們還需要知道另一個概念;那就是constructor,那什麼是constructor呢?constructor表示一個對象的構造函數,除了nullundefined以外,JavaScript中的所有數據類型都有這個屬性; 我們可以通過下麵的代碼來驗證一下:

null.constructor // Uncaught TypeError: Cannot read property 'constructor' of null ...
undefined.constructor // Uncaught TypeError: Cannot read property 'constructor' of undefined ...

(true).constructor // ƒ Boolean() { [native code] }
(1).constructor // ƒ Number() { [native code] }
"hello".constructor // ƒ String() { [native code] }

我們還可以使用下麵的圖來更加具體的表現:

4

但是其實上面這張圖的表示並不算準確,因為一個對象的constructor屬性確切地說並不是存在這個對象上面的; 而是存在這個對象的原型上面的(如果是多級繼承需要手動修改原型的constructor屬性,見文章末尾的代碼),我們可以使用下麵的代碼來解釋一下:

const F = function() {};
// 當我們定義一個函數的時候,這個函數的prototype屬性上面的constructor屬性指向自己本身
F.prototype.constructor === F; // true

下麵的圖片形象的展示了上面的代碼所表示的內容:

5

關於constructor還有一些需要註意的問題,對與JavaScript的原始類型來說,它們的constructor屬性是只讀的,不可以修改。 我們可以通過下麵的代碼來驗證一下:

(1).constructor = "something";
console.log((1).constructor); // 輸出 ƒ Number() { [native code] }

當然,如果你真的想更改這些原始類型的constructor屬性的話,也不是不可以,你可以通過下麵的方式來進行修改:

Number.prototype.constructor = "number constructor";
(1).constructor = 1;
console.log((1).constructor); // 輸出 number constructor

當然上面的方式我們是不推薦你在真實的開發中去使用的,如果你想要瞭解更多關於constructor的內容,可以看看Object.prototype.constructor

接下來,我會使用一些代碼來把今天講解的知識再大致的回顧一下:

function Animal(name) {
  this.name = name;
}

Animal.prototype.setName = function(name) {
  this.name = name;
};
Animal.prototype.getName = function(name) {
  return this.name;
};

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}
// 將Dog的prototype的指向修改為Animal.prototype
Dog.prototype = Object.create(Animal.prototype);

// 因為上面的語句將我們原來的prototype的指向修改了,所以我們要重新定義Dog的prototype屬性的constructor屬性
Reflect.defineProperty(Dog.prototype, "constructor", {
  value: Dog,
  enumerable: false, // 不可枚舉
  writable: true
});

const animal = new Animal("potato");
console.log(animal.__proto__ === Animal.prototype); // true
console.log(animal.constructor === Animal); // true
console.log(animal.name); // potato

const dog = new Dog("potato", "labrador");
console.log(dog.name); // potato
console.log(dog.breed); // labrador
console.log(dog.__proto__ === Dog.prototype); // true
console.log(dog.constructor === Dog); // true

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

-Advertisement-
Play Games
更多相關文章
  • 本文是對hide()和show()的進一步補充,其中不僅介紹回調函數,還有遞歸的相關知識點。 案例要求: 點擊”隱藏動畫“按鈕,四個頭像從後向前,每個以0.8秒的速度消失 點擊”顯示動畫“按鈕,四個頭像從前向後,每個以0.8秒的速度出現 知識點: 遞歸思想:arguments.callee 回調函數 ...
  • 頭部信息 區塊標簽 123 主內容區域 有且只有一個 表示包含一個文檔裡面獨立的部分 尾部信息 H5的語義化標簽的使用 section分章節 一章節在分為小盒子 分清楚層次 ... ...
  • js小技巧總結 1、Array.includes條件判斷 2、set與去重 ES6 提供了新的數據結構 Set。它類似於數組,但是成員的值都是唯一的,沒有重覆的值。Set 本身是一個構造函數,用來生成 Set 數據結構。 數組去重 Array.from 方法可以將 Set 結構轉為數組。我們可以專門 ...
  • 實現原理:使用2個div,裡面分別放大圖片和小圖片,在小圖片上應該還有一個遮罩層,通過定位遮罩層的位置來定位大圖片的相對位置,而且,遮罩層的移動應該和大圖片的移動方向相反 關鍵: 大圖片和小圖片大小比例應該和遮罩層的大小和放大顯示區域的比例相同; 難點: 計算遮罩層來顯示相應大圖片的位置 話不多說直 ...
  • 先介紹個方法 charCodeAt() 方法可返回指定位置的字元的 Unicode 編碼。這個返回值是 0 - 65535 之間的整數。 stringObject.charCodeAt(index) 簡言之 就是獲取字元串第一個字元的Unicode 編碼,index說是必填 你不填的話預設為0 即第 ...
  • hide()和show()方法,可以設置動畫效果,本文對這兩種方法效果加以說明。 hide(參數1,參數2): 參數1:時間,單位為毫秒,表示對象隱藏所用的時間 參數2:回調函數,該函數在對象隱藏後觸發。 show(參數1,參數2): 參數1:同上 參數2:同上 示例: 需求說明:點擊一個圖片,該圖 ...
  • ① $(this).next(); 獲取的是當前元素的下一個兄弟元素 ②$(this).nextAll(); 獲取的是當前元素的後面的所有的兄弟元素 ③$(this).prev(); 獲取的是當前元素的前一個兄弟元素 ④$(this).prevAll(); 獲取的是當前元素的前面的所有的兄弟元素 ⑤ ...
  • 網站開發 ,經常需要用到登錄註冊,簽到抽獎等模塊,雖然每次都要寫,但是把這個記錄下來會很方便下次再用。。。 這邊剛寫了一個簽到抽獎,放到這邊來分享記錄一下。 首先根據設計圖 需要這樣的樣式與效果 這樣的抽獎頁面無非就是樣式當前類切換的效果, 那基本邏輯就是點擊抽獎後,需要做一個跑馬燈的效果,並且一開 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...