個人對JS原型鏈的一些理解(prototype、__proto__)

来源:https://www.cnblogs.com/baka-sky/archive/2018/12/09/10092147.html
-Advertisement-
Play Games

前言 在我一開始學習java web的時候,對JS就一直抱著一種只是簡單用用的心態,於是並沒有一步一步地去學習,當時認為用法與java類似,但是在實際web項目中使用時卻比較麻煩,便直接粗略瞭解後開始使用jQuery。但現如今,前端發展迅速,js語法方便也有了相當大的改善,並且伴隨著node.js的 ...


前言

在我一開始學習java web的時候,對JS就一直抱著一種只是簡單用用的心態,於是並沒有一步一步地去學習,當時認為用法與java類似,但是在實際web項目中使用時卻比較麻煩,便直接粗略瞭解後開始使用jQuery。但現如今,前端發展迅速,js語法方便也有了相當大的改善,並且伴隨著node.js的登場,js的適用性也更加廣泛。其實也是自己瞭解到了electron的存在,再加上web開發中前端與後端開發也比較密切,於是這便又掉頭回來重新開始學習js。在學習的過程中,仔細學習了一下js的原型鏈,也在這裡做個記錄,如果有不對的地方,還請各位指出,本人感激不盡!

正文

在js的世界中,一切皆對象,那麼我們先將對象分為三類:實例對象、原型對象、函數對象。

實例對象簡單說就是通過構造函數所創建的對象。

函數對象好理解,js的函數本身也是個對象,這個對象有這方法名、參數、方法體等屬性。構造函數是一種特殊的函數,瞭解過其他OOP語言都知道,構造函數往往會在實例對象創建的時候調用,主要是用來完成實例對象的初始化操作。但是在js中,構造函數與普通函數並不太大區別,我們也可以像使用普通函數一樣使用構造函數,即不使用new關鍵字。所以從本質上講,普通函數也是構造函數,而構造函數只是從功能上區分的一個稱呼,體現在代碼里就是用不用new關鍵字。但為了接下來的說明,下麵將都會使用構造函數對象。

原型對象比較特殊, 現在先暫時記住通過實例對象與函數對象都能找到對應的原型對象。

這三類對象之間其實都有著聯繫,而通過這些聯繫就形成了js的完整的原型鏈。我們接下來就按照這三類對象之間的關係來逐漸瞭解原型鏈。

實例對象與構造函數對象

首先來看實例對象與構造函數對象的聯繫。通過new關鍵字,我們可以通過構造函數得到一個實例對象。例如:

function Student(name){
    this.name = name;
}
var stu = new Student('wang');

在上面的片段中,Student是一個構造函數,stu則是一個通過Student創建的實例對象。二者的聯繫很明顯,而在js里則體現在實例對象stu的constructor屬性中:

stu.constructor === Student; // true

那麼反過來,我們雖然不能通過構造函數對象直接找到它所有的實例對象,但是可以通過instanceof關鍵字來判斷一個對象是不是這個構造函數的實例對象:

stu instanceof Student; // true

原型對象與其他兩類對象

上面我們也說了,構造函數與普通函數沒有什麼區別,那麼直接使用構造函數,那this自然是指內置全局對象window。但如果用new,this就指的是新的實例對象,而且這個方法還會返回這個實例對象。到這裡大致就能猜到加了關鍵字new做了什麼操作了,它創建了一個新的空對象,並且把構造函數中的this替換為空對象,最後把這個對象返回。

那麼為實例方法增加一個普通函數也這樣做,從結果來說是沒有問題的:

function Student(name){
    this.name = name;
    this.say = function(){
        console.log`I'm ${name}`;
    };
}
stu1 = new Student('wang');
stu2 = new Student('li');
stu1.say(); // I'm wang
stu2.say(); // I'm li
stu1.say === stu2.say; // false

但是我們會發現,stu1與stu2的say函數對象竟然不是一個,那就說明如果創建了1000個Student,就會有1000個say函數對象出現,而這1000個say實現的功能完全一致,這對記憶體而言顯然是極大的浪費。

如何解決這個問題呢?既然多個函數完全一致,那麼自然可以把這個函數對象放在一個地方,當訪問stu1和stu2的say函數時,統一去拿這個地方的函數對象即可。如果我們自己實現這個功能,當在實例對象中使用函數對象時,我們又得自己去手動去公共的地方尋找函數對象,這麼做顯然太費勁了。

好在這些js都已經幫我們做了,每個構造函數對象都擁有一個prototype屬性,這個屬性指向的是一個對象,這個對象我們就叫它原型對象。而這個原型對象又擁有一個與實例對象一樣的constructor屬性,同樣也是指向構造函數對象。

另外,對於每個對象,又都有一個__proto__的屬性指向它的原型對象。當我們訪問一個對象的某個屬性時,實際上是先在當前對象尋找這個屬性,如果沒有找到,則會繼續到__proto__所指的對象(原型對象)中尋找。

function Student(name){
    this.name = name;
}
Student.prototype.say = function(){
    console.log`I'm ${name}`;
};
new Student('liu').say === new Student('zhang').say; // true

為方便理解,這裡再放一張圖,對照著這張圖下麵的代碼就容易看明白了,之後如果遇到不明白的也可以回過頭來看圖,直觀明瞭。

圖示

var chen = new Student('chen')
chen.__proto__ === Student.prototype; // true
chen.constructor === Student; // true
chen.constructor === Student.prototype.constructor; // true

深入

明白了上面這些概念,我們把視角放大,不再局限於Student。前面我們說到所有對象都有一個__proto__的屬性,那麼對於函數對象和原型對象自然也不例外,我們接下來的關註點就是這兩類對象的__proto__屬性。

首先來看函數對象。在前面的代碼中,Student函數對象的__proto__是誰呢?答案是Function的原型對象。

Student.__proto__ === Function.prototype; // true

一切皆對象,那麼Function的__proto__又是誰?還是Function的原型對象:

Function.__proto__ === Function.prototype; // true

為什麼?因為Function也是個函數對象。通常我們創建函數的方式為

function xxx(x){...}
var yyy = function(y){...};

那麼其實還有一種寫法:

var zzz = new Function('z','...');
// 例如:
var hello = new Function('msg','console.log(msg)');
hello('hi'); // hi

這樣的寫法顯然能直接看出來,Function是個函數對象。於是便有一些有趣的事情了:

Function.__proto__ === Function.prototype; // true
Function.constructor === Function; // true
Function.constructor === Function.prototype.constructor; // true

Function是自己的函數對象,也是自己的實例對象:

var Function = new Function(...);

至於為什麼會這樣,這就比較像先有雞還是先有蛋的問題了。我們只需要知道所有函數對象(包括Function)的__proto__都指向Function的原型對象

與Function類似,Object也是一個函數對象。(舉一反三,Array,String,Number等都是)

我們可以這樣創建一個空的Object:

var obj = new Object();

那麼Object的原型對象的__proto__是誰呢?是null。

Object.prototype.__proto__; // null

之前說過,當我們用.操作符去拿一個屬性時,js會先在當前對象里尋找,沒有的話去__proto__的對象(原型對象)里尋找。那麼如果__proto__(原型對象)里還沒有,就繼續去它的__proto__里尋找,以此重覆。那麼什麼時候是個頭呢?直到__proto__為null時。

我們知道所有對象都有toString方法,Student的實例對象stu也是個對象,但我們明顯沒有給它添加toString方法,為什麼它會有呢?因為stu的__proto__最終指向的是Object的原型對象。這也就是js繼承的本質了。

stu.__proto__; // {constructor: ƒ}
stu.__proto__.__proto__; // {constructor: ƒ, …, toString: ƒ, …}
stu.__proto__.__proto__ === Object.prototype; // true
stu.toString === Object.prototype.toString; // true

所以,遍歷所有對象的__proto__最終都會來到Object的原型對象


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

-Advertisement-
Play Games
更多相關文章
  • 從六號開始搞Flutter,到今天寫這篇blog已經過了4天時間,文檔初步瀏覽了一遍,寫下了這個demo。demo源碼分享在github上,現在對flutter有種說不出的喜歡了。大家一起搞吧! 廢話不多說,開始不如正題: 一:單擊,雙擊,長按->傳動的手勢效果,另外這裡用來Fluttertoast ...
  • 流程介紹 1. 使用 網路框架進行 請求,獲得 數據 //一個封裝好的工具類的靜態方法 public static void sendOkHttpRequest(final String address, final okhttp3.Callback callback) { OkHttpClient ...
  • 文章鏈接: "https://mp.weixin.qq.com/s/1gkMtLu0BTXOUOj6isDjUw" 日常android開發過程中,會遇到編輯框輸入內容彈出軟鍵盤,往往會出現鍵盤遮擋內容,或者出現頁面整體上移的,或多或少在體驗上都不是很優雅,今天提供個方法是自行控制頁面上移距離,竟可能 ...
  • 用react也有段時間了, 是時候看看人家源碼了. 看源碼之前看到官方文檔有這麼篇文章介紹其代碼結構了, 為了看源碼能順利些, 遂決定將其翻譯來看看, 小弟英語也是半瓢水, 好多單詞得查詞典, 不當之處請批評. 直接從字面翻譯的, 後面看源碼後可能會在再修改下. ...
  • 本文會透過以下幾個段落,讓各位一步一步學習如何寫一個自已的Node.js Module並且發佈到npm package上 Node.js Module 結構 我們先建立一個 NodeModuleDemo 的資料夾 ,接下來利用 npm init 進行初始化 (這裡不用特別設置,一路按 Enter 到 ...
  • 執行環境是JavaScrpt 最重要的概念之一,執行環境定義了變數或函數有權訪問的其它數據,決定了它們各自的行為。每個執行環境都有一個與之關聯的變數對象,或者說一個執行環境就是一個對象。環境中定義的所有變數和函數都保存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但解析器在處理數據時會在後臺使用 ...
  • 編碼 現在我們要做一個稍微複雜的東西,如下HTML,有一堆Select用於選擇日期和時間,在選擇後,實時在 id 為 result-wrapper 的 p 標簽中顯示所選時間和當前時間的差值。 使用上方的HTML結構(可以根據需要自行微調)為基礎編寫JavaScript代碼 當變更任何一個selec ...
  • 1. 受控組件:組件處於受控制狀態,不可更改輸入框內的值。 2. 什麼情況下會讓組件變成受控組件? - 文本框設置了value屬性的時候 - 單選框或多選框設置了checked屬性的時候。 3. 如何解決? - 使用state設置值 - 綁定onChange事件 - 在事件處理方法中獲取組件的值並更 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...