個人對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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...