🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

把原型鏈畫成地鐵圖:坐 3 站路就能看懂 JS 的“繼承”怎麼跑

前言

在 JavaScript 裡,“原型”這個詞聽起來高大上,實際上就是一個“默認備胎”:當物件自己找不到屬性時,就沿著原型這條暗道去“親戚家”借。沒有類、沒有藍圖,僅靠這條備胎鏈,就能把公共方法層層複用,讓記憶體省一半、程式碼少一半。本文只聊“原型”本身——prototype__proto__ 這些眼前能用的工具,把“借東西”的流程畫成一張家譜圖,幫你先看清“親戚”是誰、住哪、怎麼串門。至於後面更高階的封裝、多態、模組化,等我們把這條鏈走熟再升級也不遲。

一: 原型 prototype

又稱顯示原型,函數天生擁有的一個屬性,將構造函數中的一些固定的屬性和方法掛載到原型上,在創建實例的時候,就不需要重複執行這些屬性和方法了,我們先來創造一個環境,主角依然是我們的小米 su7,su7 的屬性有無數個,但是各個車主只需要選擇並改動的屬性並沒有那麼多,這個時候我們就能用得到原型。

Car.prototype.name = 'su7-Ultra'
Car.prototype.lang = 4800
Car.prototype.height = 1400
Car.prototype.weight = 1.5

function Car(color) {
  this.color = color
}
const car1 = new Car('pink')
const car2 = new Car('green')
console.log(car1);

用原型之後我們只需要輸入想要的顏色即可,不需要反反復復創建函數。同時掛載在原型上的屬性是可以直接被實例物件訪問到的(如下圖)

原型1.png

並且實例物件無法修改構造函數原型上的屬性值

Person.prototype.say = '我太帥了'
function Person() {
  this.name = '饒總'
}
const p = new Person()  
p.say = 'hello'
const p2 = new Person()
console.log(p2.say);

這個時候同時有兩個 key 都為 say,但 value 不相同,一個被掛在構造函數的原型上,一個被掛在第一個實例物件 p 上,按照上面說法實例物件無法修改構造函數原型上的屬性值,但是打印出來真是這樣嗎,究竟是 '我太帥了' ,還是 'hello',我們來揭曉答案

原型2.png

果然是實例物件無法修改構造函數原型上的屬性值。

二:物件原型 __proto__

又稱隱式原型,每一個物件都擁有一個 __proto__ 屬性,該屬性值也是一個物件,v8 在訪問物件中的一個屬性時,會先訪問該物件中的顯示屬性,如果找不到,就回去物件的隱式原型中查找,實例物件的隱式原型 === 構造函數的顯示原型,所以如果實例物件的隱式原型找不到那麼就再會去構造函數的顯示原型上找

這不得不再引出一個概念—— 原型鏈:v8 在訪問物件中的屬性時,會先訪問該物件中的顯示屬性,如果找不到,就去物件的隱式原型上找,如果還找不到,就去__proto__.__proto__ 上找,層層往上,直到找到null為止。這種查找關係被稱為原型鏈。

為了更好的理解它,我們來舉個繼承例子

function Parent() {
  this.lastName = '張'
}
Child.prototype = new Parent()  // {lastName: '張'}.__proto__ = Parent.prototype
function Child() {
  this.age = 18
}
const c = new Child()
console.log(c.lastName);

在實例物件中我們只能找到兒子的年齡屬性,姓氏張是兒子從父親那裡繼承的,我們要查到兒子的姓氏,根據原型鏈原理我們先從實例物件 c 中找有沒有顯示屬性是關於姓氏的,很明顯並沒有,接著就去實例物件的隱式原型上找,也沒有,最後就來到了構造函數的顯示原型上查找,在程式碼的第四行可以看到構造函數的顯示原型被賦值上了 lastName 屬性,所以最終是否可以查找得到姓氏張呢?我們來直接看結果

原型3.png

好你說這個也太簡單了吧,就父子繼承而已。話不多說我再附上一串程式碼和打印結果

Grand.prototype.house = function() {
  console.log('四合院');
}
function Grand() {
  this.card = 10000
}
Parent.prototype = new Grand()  // {card: 10000}.__proto__ = Grand.prototype.__proto__ = Object.prototype.__proto__ = null
function Parent() {
  this.lastName = '張'
}
Child.prototype = new Parent()  // {lastName: '張'}.__proto__ = Parent.prototype
function Child() {
  this.age = 18
}
const c = new Child()  // {age: 18}.__proto__ = Child.prototype
console.log(c.card);
c.house()  
// console.log(c.toString());

原型4.png

這裡我們要注意一點:如果讓你查找一個整個頁面都沒有的屬性又該會是什麼打印結果呢?我們注意看上面最後一行註解掉的程式碼,他的輸出結果如下

原型5.png

他是直接找到了全局的物件上,經歷了一遍原型鏈查找在 Object.prototype上找到,如果再不找到最終就會停留在null上,下面放一張 js 界中廣為流傳的一張圖,如果你能看懂那麼你就是徹底會了!

原型鏈圖.webp

三:new 在幹什麼?

這時候你會說什麼?上篇文章不是講了 new 究竟幹了些什麼嗎,怎麼又問,不必驚訝,其實上次沒講全,這次來帶你真正看看 new 究竟究竟都幹了些什麼(這絕對是最終理解)直接一套小連招先上五個步驟

  1. 創建一個空物件
  2. 讓構造函數中的 this 指向這個空物件
  3. 執行構造函數中的程式碼 (等同於往空物件中添加屬性值)
  4. 將這個空物件的隱式原型(__proto__) 赋值成 構造函數的顯示原型(prototype)
  5. 返回該物件

再上程式碼(加註解)

Car.prototype.run = function() {
  console.log('running');
}

function Car() {   // new Function()
  // const obj = {}      //1
  // Car.call(obj)  // call 方法將 Car 函數中的 this = obj    2
  this.name = 'su7'  // 3
  // obj.__proto__ = Car.prototype  // 4
  // return obj    5
}
const car = new Car() // {name: 'su7'}.__proto__  == Car.prototype
car.run()

最後輸出

原型6.png

結語

  1. 顯式原型(prototype)是函數自帶的“樣板房”,所有實例都能來蹭住。
  2. 隱式原型(__proto__)是實例手裡的“門禁卡”,刷卡就能進樣板房找方法。
  3. 原型鏈就是一張“門禁卡鏈”:刷不到就再刷上一層的卡,直到 null 到頭。
  4. new 的五步曲:空物件→認證→綁卡→執行→返回,一口氣把“樣板房”繼承給新實例。

把這四點串成一張地鐵圖,以後看任何“找不到屬性”的問題,先問一句:它刷卡刷到第幾站了?原型鏈通了,繼承就不再是黑魔法。


原文出處:https://juejin.cn/post/7575004291833430067


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬10   ❤️5
420
🥈
我愛JS
📝2   💬8   ❤️4
94
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付