JS 原型與原型鏈詳解

JavaScript 原型與原型鏈詳解

文章目錄

  • JavaScript 原型與原型鏈詳解
    • 一、基礎概念類
      • 1.1 什么是原型?JavaScript 中如何訪問一個對象的原型?
      • 1.2 構造函數、實例對象和原型對象之間的關系是什么?
      • 1.3 prototype 和 **proto** 的區別是什么?
    • 二、原型鏈機制類
      • 2.1 什么是原型鏈?描述原型鏈的查找機制
      • 2.2 代碼示例分析
    • 三、構造函數與實例類
      • 3.1 new 操作符執行時發生了什么?
      • 3.2 手動實現 new 操作符
        • 3.2.1apply邏輯
        • 3.2.2`result instanceof Object ? result : obj;`解決的問題
    • 四、繼承與原型鏈類
      • 4.1 JS如何實現繼承?
    • 原型鏈繼承
    • 五、高級應用類
      • 5.1 如何修改內置對象(如 Array)的原型?這樣做有什么風險?
        • 5.1.1修改內置原型
        • 5.1.2創建子類繼承內置對象(ES6 Class方式)
        • 5.1.3使用Object.create創建原型鏈繼承
      • 5.2 如何判斷一個屬性是對象自身的還是繼承自原型鏈的?
    • 六、總結

一、基礎概念類

1.1 什么是原型?JavaScript 中如何訪問一個對象的原型?

**原型(Prototype)**是 JavaScript 實現繼承的基礎機制。每個 JavaScript 對象都有一個內部屬性 [[Prototype]](可通過 __proto__Object.getPrototypeOf() 訪問),它指向該對象的原型對象。原型對象本身也是一個普通對象,同樣擁有自己的原型,這樣就形成了原型鏈。

訪問對象原型的方式

  • 對于構造函數:通過 Constructor.prototype 訪問
  • 對于實例對象:
    • obj.__proto__(非標準但廣泛支持)
    • Object.getPrototypeOf(obj)(標準方法)
function Person() {}
const p = new Person();// 訪問構造函數的原型
console.log(Person.prototype); // 訪問實例的原型
console.log(p.__proto__); 
console.log(Object.getPrototypeOf(p)); 

1.2 構造函數、實例對象和原型對象之間的關系是什么?

三者關系可概括為:

  1. 構造函數:用于創建對象的函數,擁有 prototype 屬性
  2. 原型對象:通過 Constructor.prototype 訪問,包含共享屬性和方法
  3. 實例對象:通過 new Constructor() 創建,其 __proto__ 指向構造函數的原型

關系圖示:

構造函數 (Person)├── prototype (原型對象)│     ├── constructor (指回構造函數)│     └── 共享屬性和方法└── 實例化└── 實例對象 (p)└── __proto__ (指向原型對象)

關鍵點:

  • 構造函數的 prototype 屬性指向原型對象
  • 原型對象的 constructor 屬性指回構造函數
  • 實例對象的 __proto__ 指向構造函數的原型對象

1.3 prototype 和 proto 的區別是什么?

特性prototype__proto__
所屬對象函數對象(包括函數)
作用為構造函數定義共享屬性和方法指向對象的原型,形成原型鏈
是否標準否(非標準但廣泛支持)
訪問方式Constructor.prototypeobj.__proto__Object.getPrototypeOf(obj)
用途場景實現繼承和共享方法屬性查找機制

關鍵區別

  • prototype 是函數特有的屬性,用于實現基于原型的繼承
  • __proto__ 是對象實例的屬性,指向其構造函數的原型對象

二、原型鏈機制類

2.1 什么是原型鏈?描述原型鏈的查找機制

原型鏈是由對象的 __proto__ 鏈接形成的鏈條結構,它允許對象訪問其原型上的屬性和方法,直到 Object.prototype.__proto__(值為 null)為止。

原型鏈查找機制

  1. 訪問對象屬性時,首先在對象自身查找
  2. 如果自身不存在該屬性,則通過 __proto__ 查找其原型對象
  3. 繼續沿原型鏈向上查找,直到找到屬性或到達原型鏈頂端(null
  4. 如果最終未找到,則返回 undefined

這種機制實現了 JavaScript 的繼承和屬性共享。

2.2 代碼示例分析

function Person() {}
Person.prototype.name = "Alice";let p = new Person();
console.log(p.name); // "Alice"
console.log(p.hasOwnProperty("name")); // false

執行過程解析

  1. p.name 訪問時:
    • 首先在 p 對象自身查找 name 屬性 → 未找到
    • 通過 p.__proto__ 查找 Person.prototype → 找到 name: "Alice"
    • 返回 "Alice"
  2. p.hasOwnProperty("name")
    • hasOwnProperty 是檢查屬性是否為對象自身的屬性
    • name 實際存在于 Person.prototype 上,而非 p 對象自身
    • 返回 false

三、構造函數與實例類

3.1 new 操作符執行時發生了什么?

new 操作符創建實例時,背后執行了以下步驟:

  1. 創建一個新的空對象 {}
  2. 將新對象的 __proto__ 指向構造函數的 prototype 屬性
  3. 將構造函數的 this 綁定到新對象,并執行構造函數
  4. 如果構造函數返回一個對象,則返回該對象;否則返回新創建的對象

完整過程示例

function Person(name) {this.name = name;
}const p = new Person("Alice");

等價于:

function myNew(Constructor, ...args) {// 1. 創建空對象let obj = {};// 2. 設置原型鏈obj.__proto__ = Constructor.prototype;// 3. 綁定this并執行構造函數let result = Constructor.apply(obj, args);// 4. 返回結果(優先返回對象,否則返回新對象)return result instanceof Object ? result : obj;
}const p = myNew(Person, "Alice");

3.2 手動實現 new 操作符

function myNew(Constructor, ...args) {// 1. 創建一個新對象,并將其原型指向構造函數的prototypelet obj = Object.create(Constructor.prototype);// 2. 調用構造函數,將this綁定到新對象,args是一個數組let result = Constructor.apply(obj, args);// 3. 如果構造函數返回了一個對象,則返回該對象;否則返回新對象return result instanceof Object ? result : obj;
}
3.2.1apply邏輯

apply(thisArg,argsArray) 接收 兩個參數

  1. thisArg(必需):函數運行時綁定的 this 值。
  2. argsArray(可選):一個數組或類數組對象,包含傳遞給函數的參數列表。如果省略或為 null/undefined,則相當于傳遞空數組。

argsArray 的核心作用是解耦 myNew 和構造函數 Constructor 之間的參數關系

  1. myNew 不需要關心具體有多少參數,只需將所有額外參數打包到 args 數組中。
  2. Constructor 可以自由定義自己需要的參數,通過 this 接收并處理。
3.2.2result instanceof Object ? result : obj;解決的問題

作用:處理構造函數 Constructor 的返回值,確保最終返回的對象符合預期

在 JavaScript 中,構造函數(通過 new 調用的函數)可以顯式返回一個值。這個返回值可以是:

  1. 一個對象(包括數組、函數、普通對象等)。
  2. 一個原始值(如 numberstringbooleannullundefined)。

如果構造函數返回一個對象,new 操作符會忽略默認創建的實例對象,直接返回這個指定的對象;如果返回原始值,則忽略返回值,仍然返回默認創建的實例對象。

示例:構造函數返回對象 vs 原始值
// 情況1:構造函數返回對象
function Person1() {return { name: "Bob" }; // 返回一個新對象
}
const p1 = new Person1();
console.log(p1); // { name: "Bob" }(不是 Person1 的實例)// 情況2:構造函數返回原始值
function Person2() {return 123; // 返回原始值
}
const p2 = new Person2();
console.log(p2); // Person2 的實例(不是 123)

四、繼承與原型鏈類

4.1 JS如何實現繼承?

原型鏈繼承

核心:將子類的原型指向父類的實例

JavaScript 中通過原型鏈繼承實現繼承的基本模式:

function Parent() {this.parentProperty = "Parent Value";
}Parent.prototype.parentMethod = function() {console.log("Parent Method");
};function Child() {this.childProperty = "Child Value";
}// 關鍵步驟:將Child的原型指向Parent的實例
Child.prototype = new Parent();const c = new Child();
console.log(c.parentProperty); // "Parent Value"
c.parentMethod(); // "Parent Method"

繼承關系圖示

Child實例 (c)├── __proto__ → Child.prototype (Parent的實例)├── __proto__ → Parent.prototype├── constructor → Parent└── parentMethod└── childProperty (來自Child構造函數)

注意:這種繼承方式存在一些問題(如引用類型共享、無法向父類構造函數傳參等),現代開發更推薦使用 ES6 的 classextends 語法。

五、高級應用類

5.1 如何修改內置對象(如 Array)的原型?這樣做有什么風險?

5.1.1修改內置原型

修改方式示例

// 添加自定義方法到Array原型
Array.prototype.customMethod = function() {console.log("This is a custom method");
};const arr = [1, 2, 3];
arr.customMethod(); // "This is a custom method"

潛在風險

  1. 全局污染:修改內置原型會影響所有使用該內置對象的代碼
  2. 命名沖突:可能與未來 JavaScript 版本新增的方法名沖突
  3. 兼容性問題:可能導致與其他庫或框架的不可預期交互
  4. 維護困難:使代碼行為變得不可預測,增加調試難度

最佳實踐

  • 避免直接修改內置原型
  • 如需擴展功能,考慮使用工具函數或創建子類
  • 如果必須修改,添加前綴以減少沖突風險(如 myCustomMethod
5.1.2創建子類繼承內置對象(ES6 Class方式)

這是最推薦的方式,既擴展了功能,又不會影響原生對象。

1. 基礎繼承示例

// 創建繼承自Array的自定義類
class CustomArray extends Array {// 添加自定義方法customSum() {return this.reduce((acc, val) => acc + val, 0);}// 可以添加更多自定義方法customMax() {return Math.max(...this);}
}// 使用示例
const arr = new CustomArray(1, 2, 3, 4);
console.log(arr.customSum()); // 輸出: 10
console.log(arr.customMax()); // 輸出: 4
console.log(arr instanceof Array); // true (仍然屬于Array類型)
5.1.3使用Object.create創建原型鏈繼承

更底層的實現方式,不使用ES6 class語法。

// 創建基于Array的新對象
const EnhancedArray = function(...items) {const arr = [...items];return Object.setPrototypeOf(arr, EnhancedArray.prototype);
};// 設置原型鏈
EnhancedArray.prototype = Object.create(Array.prototype);
EnhancedArray.prototype.constructor = EnhancedArray;// 添加自定義方法
EnhancedArray.prototype.customSum = function() {return this.reduce((acc, val) => acc + val, 0);
};// 使用示例
const arr = EnhancedArray(1, 2, 3);
console.log(arr.customSum()); // 輸出: 6
console.log(arr instanceof Array); // true

5.2 如何判斷一個屬性是對象自身的還是繼承自原型鏈的?

判斷方法

  1. hasOwnProperty() 方法:

    • 只檢查對象自身的屬性,不檢查原型鏈
    const obj = { a: 1 };
    console.log(obj.hasOwnProperty('a')); // true
    console.log(obj.hasOwnProperty('toString')); // false
    
  2. Object.getOwnPropertyNames()

    • 返回對象自身的所有屬性名(不包括原型鏈)
    const obj = { a: 1, b: 2 };
    console.log(Object.getOwnPropertyNames(obj)); // ["a", "b"]
    
  3. in 操作符:

    • 檢查屬性是否存在于對象或其原型鏈中
    const obj = { a: 1 };
    console.log('a' in obj); // true
    console.log('toString' in obj); // true
    

綜合示例

function checkProperty(obj, prop) {if (obj.hasOwnProperty(prop)) {console.log(`${prop} 是對象自身的屬性`);} else if (prop in obj) {console.log(`${prop} 是繼承自原型鏈的屬性`);} else {console.log(`${prop} 不是對象的屬性`);}
}const obj = { a: 1 };
checkProperty(obj, 'a'); // "a 是對象自身的屬性"
checkProperty(obj, 'toString'); // "toString 是繼承自原型鏈的屬性"
checkProperty(obj, 'b'); // "b 不是對象的屬性"

六、總結

JavaScript 的原型和原型鏈機制是其面向對象編程的核心,理解這些概念對于掌握 JavaScript 至關重要:

  1. 原型是對象間共享屬性和方法的機制
  2. 原型鏈實現了屬性的查找和繼承
  3. 構造函數、實例和原型三者構成了 JavaScript 對象系統的基礎
  4. new 操作符通過特定步驟創建實例并建立原型鏈
  5. 原型鏈繼承是 JavaScript 實現繼承的主要方式
  6. 謹慎操作內置原型,避免潛在風險
  7. 準確判斷屬性來源對調試和維護非常重要

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/84696.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/84696.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/84696.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

DEVICENET轉MODBUS TCP網關連接DeviceNet數字遠程IO模塊配置案例

設備與網絡架構,主控設備:支持Modbus TCP協議的PLC(如西門子S7-1200)。網關設備:開疆智能Modbus TCP轉DeviceNet網關KJ-DVCZ-MTCPS(需支持DeviceNet從站功能)。目標設備:DeviceNet數…

Ubuntu下使用PyTurboJPEG加速圖像編解碼

目錄 一、概述 二、安裝PyTurboJPEG 三、測試 一、概述 在計算機視覺領域,圖像編解碼是繞不開的基礎環節。雖然 OpenCV 能解決大部分圖像處理問題,但在性能要求嚴苛的場景下存在短板。本文將介紹基于 libjpeg-turbo 的高效 JPEG 編解碼庫 PyTurboJPE…

MCU、MPU、GPU、Soc、DSP、FPGA、CPLD……它們到底是什么?

MCU、MPU、GPU、Soc、DSP、FPGA、CPLD…… 這些簡稱在各大論壇、會議、發布會中屢見不鮮,看到簡稱,雖然也能說出大概; 但要問具體是什么?用在什么場景?又有什么區別……好像還是差點意思;本篇文章就記錄一…

Django RBAC項目后端實戰 - 03 DRF權限控制實現

項目背景 在上一篇文章中,我們完成了JWT認證系統的集成。本篇文章將實現基于Redis的RBAC權限控制系統,為系統提供細粒度的權限控制。 開發目標 實現基于Redis的權限緩存機制開發DRF權限控制類實現權限管理API配置權限白名單 前置配置 在開始開發權限…

[網頁五子棋][用戶模塊]數據庫設計和配置(MyBatis)、約定前后端交互接口、服務器開發

文章目錄 數據庫數據庫設計配置 MyBatis1. Spring 配置2. 創建實體類3. 創建 Mapper 接口4. 使用 MyBatis 約定前后端交互接口登錄接口注冊接口獲取用戶信息 服務器開發loginregistergetUserInfo完整代碼 數據庫 數據庫設計 完成注冊登錄以及用戶分數管理 使用數據庫來保存上…

Qt/C++學習系列之列表使用記錄

Qt/C學習系列之列表使用記錄 前言列表的初始化界面初始化設置名稱獲取簡單設置 單元格存儲總結 前言 列表的使用主要基于QTableWidget控件,同步使用QTableWidgetItem進行單元格的設置,最后可以使用QAxObject進行單元格的數據讀出將數據進行存儲。接下來…

防火墻通常可以分為哪些類型?

防火墻是目前保護網絡安全的重要設備,能夠通過監控、過濾和控制進出網絡的數據流量,來保護內部網絡不會受到未經授權的IP地址進行訪問和惡意的網絡威脅,設置防火墻能夠幫助企業確保網絡的安全性,同時防火墻也會根據不同的功能來劃…

基于GeoTools的道路相交多個點容差冗余計算實戰

目錄 前言 一、關于道路相交 1、相交四個點 2、點更多的情況 二、基于距離的相交點去重 1、冗余距離計算 2、調用過程 3、去重后的結果 三、總結 前言 在地理信息系統(GIS)領域,道路網絡數據的處理與分析一直是關鍵課題。隨著城市化進…

android:foregroundServiceType詳解

在 Android 中,foregroundServiceType 是用于聲明前臺服務類型的屬性,主要從 Android 10(API 29)開始引入,并在 Android 11(API 30)及更高版本 中進一步細化。以下是所有可用的 foregroundServi…

React+Taro 微信小程序做一個頁面,背景圖需貼手機屏幕最上邊覆蓋展示

話不多說 直接上圖 第一步 import { getSystemInfoSync } from tarojs/taro;第二步 render() {const cardBanner getImageUrlByGlobal(member-merge-bg.png);const { safeArea, statusBarHeight } getSystemInfoSync();const NAV_BAR_HEIGHT 44;const navBarHeight NAV…

從零開始的云計算生活——番外,實戰腳本。

目錄 題目一:系統信息收集腳本 題目二:用戶管理配置腳本 題目三:磁盤空間管理腳本 題目四:網絡配置檢查腳本 題目五:系統日志分析腳本 題目一:系統信息收集腳本 編寫一個腳本名為 collect_system_info…

MySQL基礎知識(DDL、DML)

什么是數據庫? 數據庫:英文為 DataBase,簡稱DB,它是存儲和管理數據的倉庫。 注釋: 單行注釋:-- 注釋內容 或 # 注釋內容(MySQL特有)多行注釋: /* 注釋內容 */ 分類 SQL語句根據其功能被分為…

用volatile修飾數組代表什么意思,Java

文章目錄 volatile 修飾數組引用的含義volatile 對數組元素無效總結 如何讓數組元素也具有 volatile 特性? 當用 volatile 關鍵字修飾一個數組時,它只保證數組引用的可見性和部分原子性,而不保證數組元素的可見性和原子性。 換句話說&#x…

Ubuntu 24.04 LTS 長期支持版發布:對服務器用戶意味著什么?新特性、升級建議與性能影響初探

更多云服務器知識,盡在hostol.com 在服務器運維的廣闊世界里,每一次主流操作系統長期支持(LTS)版本的發布,都無異于一次重要的“時代交替”。它不僅帶來了一系列令人矚目的技術革新,更重要的是&#xff0c…

題目 3241: 藍橋杯2024年第十五屆省賽真題-挖礦

題目 3241: 藍橋杯2024年第十五屆省賽真題-挖礦 時間限制: 3s 內存限制: 512MB 提交: 1267 解決: 224 題目描述 小藍正在數軸上挖礦,數軸上一共有 n 個礦洞,第 i 個礦洞的坐標為 ai 。小藍從 0 出發,每次可以向左或向右移動 1 的距離&#xf…

vue3+ts+vite創建的后臺管理系統筆記

Vue3+ Vite + Element-Plus + TypeScript 從0到1搭建企業級后臺管理系統(前后端開源):參考有來科技學習搭建項目 創建項目bug匯總,知識點src 路徑別名配置和tsconfig.json文件報錯【這個不配置好,會引起其他頁面引用時報錯:見--整合 Pinia】:整合 Pinia 【參考-- src 路徑…

指針01 day13

十三:指針變量 一:數據類型 ? 指針類型---------對應處理的數據是指針 (地址)這種數據 ? 整型類型---------對應處理的數據是整數這種類型 二:定義指針類型的變量 ? 語法: 基類型(1) *(…

基于深度學習的智能文本生成:從模型到應用

前言 隨著人工智能技術的飛速發展,自然語言處理(NLP)領域取得了顯著的進展。其中,智能文本生成技術尤其引人注目。從聊天機器人到內容創作,智能文本生成不僅能夠提高效率,還能創造出令人驚嘆的內容。本文將…

Oracle業務用戶的存儲過程個數及行數統計

Oracle業務用戶的存儲過程個數及行數統計 統計所有業務用戶存儲過程的個數獨立定義的存儲過程定義在包里的存儲過程統計所有業務用戶存儲過程的總行數獨立定義的存儲過程定義在包里的存儲過程通過DBA_SOURCE統計類型個數和代碼行數?? 對存儲過程進行統計主要用到以下三個系統…

多線程安全:核心解決方案全解析

在多線程環境下保證共享變量的線程安全,需解決原子性、可見性、有序性三大問題。以下是核心解決方案及適用場景: 一、同步鎖機制(互斥訪問) synchronized 關鍵字 原理:通過 JVM 監視器鎖(Monitor)確保同一時間僅一個線程訪問臨界區。示例:public class Counter {privat…