這是一個非常常見的 JavaScript 問題。所有 JS 對象都有一個__proto__
屬性,指向它的原型對象。當試圖訪問一個對象的屬性時,如果沒有在該對象上找到,它還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。這種行為是在模擬經典的繼承,
1. 每個對象都有一個內部屬性 [[Prototype]]
- 可以通過
__proto__
訪問(不推薦) - 或通過
Object.getPrototypeOf(obj)
獲取 - 或通過
Object.setPrototypeOf(obj, proto)
設置
2. 原型對象(prototype
)
- 函數對象有一個
.prototype
屬性,它用于構建新對象的[[Prototype]]
constructor
創建的新對象,其__proto__
指向該構造函數的prototype
當你訪問一個對象的屬性時,例如:
obj.name
JS 引擎的執行順序如下:
-
當前對象
obj
中查找name
屬性。 -
如果沒有,就查找其原型對象
obj.__proto__
中是否有name
。 -
如果還沒有,就繼續沿著原型鏈向上查找,直到:
- 找到為止(返回值)
- 原型鏈終點
null
(返回undefined
)
📚 構造函數 + 原型鏈的組合方式(經典原型繼承)
function Animal(name) {this.name = name;
}Animal.prototype.sayHi = function() {console.log("Hi, I'm " + this.name);
};const cat = new Animal("Kitty");cat.sayHi(); // Hi, I'm Kitty
cat.__proto__ === Animal.prototype
?Animal.prototype.__proto__ === Object.prototype
?
這條鏈一直延伸到 null
終點。
為什么 JavaScript 要設計“原型鏈繼承”機制? 我的另一種思考…
? 1. 動態語言需要靈活的繼承模型
JavaScript 是一種動態語言,強調:
- 對象可以隨時添加/刪除屬性
- 類和實例的界限模糊
- 對象結構可隨運行時變化
?? 傳統的基于類(Class-based)繼承 結構較為死板、靜態,而 JS 初衷是用于瀏覽器腳本,需要靈活、輕量、動態。
原型繼承天然支持:
const a = { greeting: 'hi' };
const b = Object.create(a); // b 繼承自 a
無需類、構造函數,只要對象就能繼承對象,非常輕便。
🧱 2. 萬物皆對象,繼承也要“對象化”
不像 Java/C++ 使用 class 來定義繼承,JavaScript 一切都可以看作是對象(包括函數、數組等)。
- JavaScript 設計者 Brendan Eich 的理念是: “對象應該能繼承另一個對象” 。
- 所以不需要 class 和 instance 的強制約束 —— 只需鏈式對象(對象 → 原型對象 → 更高層原型…)。
obj --> obj.__proto__ --> obj.__proto__.__proto__ --> null
這就形成了所謂的 原型鏈(Prototype Chain)。
🧬 3. 共享行為、節省內存
多個對象可以共享一個原型中的方法和屬性,避免重復開銷:
const personMethods = {greet() { console.log('Hello'); }
};const p1 = Object.create(personMethods);
const p2 = Object.create(personMethods);p1.greet(); // Hello
p2.greet(); // Hello
- 如果沒有原型機制,
greet()
方法需要在每個對象上都拷貝一份(浪費內存)。 - 通過原型鏈,“行為”可以被多個對象復用。
🧩 4. 實現靈活的“差異化對象”結構
原型機制允許對象“有差異地”繼承:
const animal = { move() {} };
const dog = Object.create(animal);
dog.bark = () => console.log('woof');
dog
是個有bark
行為的animal
- 你可以定制對象之間的差異而不破壞它們的繼承結構
🚀 5. 支持動態補丁(Monkey Patching)和熱修復
因為原型是對象,運行時可以隨時改:
Array.prototype.sum = function() {return this.reduce((a, b) => a + b, 0);
};[1, 2, 3].sum(); // 6
JS 的生態系統(如 Polyfill、第三方補丁)大量利用了這種能力。這在 class 語言中是很難做到的。
🔄 6. 為未來引入 class
奠定基礎
ECMAScript 2015 (ES6) 中加入了 class
語法,其實只是原型的語法糖:
class Person {sayHi() { console.log('hi'); }
}const p = new Person();
實際上等價于:
function Person() {}
Person.prototype.sayHi = function() { console.log('hi'); }
const p = new Person();
?? class
語法只是“看起來像傳統 OO”,底層仍然是原型鏈。
參考
- https://www.quora.com/What-is-prototypal-inheritance/answer/Kyle-Simpson
- https://davidwalsh.name/javascript-objects