# JavaScript的繼承和原型鏈是什么
## 引言
JavaScript作為一門基于原型的語言,其繼承機制與傳統的基于類的語言(如Java、C++)有著本質區別。理解原型鏈是掌握JavaScript面向對象編程的核心,也是許多高級特性的基礎。本文將深入剖析原型鏈的運行機制、實現繼承的多種方式,以及現代JavaScript中的類語法本質。
## 一、原型基礎概念
### 1.1 什么是原型(Prototype)
在JavaScript中,每個對象(除`null`外)都有一個內置屬性`[[Prototype]]`(可通過`__proto__`訪問),這個屬性指向另一個對象,我們稱其為該對象的"原型"。
```javascript
const animal = {
eats: true
};
const rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // 設置rabbit的原型為animal
console.log(rabbit.eats); // true,通過原型鏈訪問
當訪問對象的屬性時,JavaScript引擎會:
1. 首先在對象自身屬性中查找
2. 如果找不到,則沿著[[Prototype]]
向上查找
3. 直到找到屬性或到達原型鏈末端(null
)
const grandparent = { a: 1 };
const parent = { b: 2 };
const child = { c: 3 };
parent.__proto__ = grandparent;
child.__proto__ = parent;
console.log(child.a); // 1,通過三級原型鏈查找
每個函數都有一個特殊的prototype
屬性(注意不是[[Prototype]]
),這個屬性會在使用new
操作符時成為新實例的原型。
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
const john = new Person('John');
john.sayHi(); // 通過原型調用方法
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Dog(name, breed) {
Animal.call(this, name); // 調用父類構造函數
this.breed = breed;
}
// 設置原型鏈
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
缺點: - 引用類型屬性會被所有實例共享 - 創建子類實例時無法向父類構造函數傳參
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function Child(name, age) {
Parent.call(this, name); // 借用父類構造函數
this.age = age;
}
優點: - 避免了引用類型共享問題 - 可以向父類傳參
缺點: - 方法必須在構造函數中定義,無法復用 - 無法訪問父類原型上的方法
結合原型鏈繼承和構造函數繼承的優點:
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次調用Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次調用Parent
Child.prototype.constructor = Child;
缺點: - 父類構造函數被調用兩次
基于現有對象創建新對象:
function createObject(obj) {
function F() {}
F.prototype = obj;
return new F();
}
const person = {
name: 'Default',
friends: ['Alice', 'Bob']
};
const another = createObject(person);
ES5標準化為Object.create()
:
const another = Object.create(person);
在原型式繼承基礎上增強對象:
function createAnother(original) {
const clone = Object.create(original);
clone.sayHi = function() {
console.log('Hi');
};
return clone;
}
解決組合繼承調用兩次構造函數的問題:
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
}
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 調用父類構造函數
this.breed = breed;
}
bark() {
console.log('Woof!');
}
}
super()
),代表父類構造函數super.method()
),指向父類原型class Parent {
static staticMethod() {
console.log('Parent static');
}
}
class Child extends Parent {
static staticMethod() {
super.staticMethod();
console.log('Child static');
}
}
創建一個新對象,使用現有對象作為新對象的原型:
const proto = { x: 10 };
const obj = Object.create(proto);
獲取對象的原型:
const proto = {};
const obj = Object.create(proto);
console.log(Object.getPrototypeOf(obj) === proto); // true
設置對象的原型(不推薦用于性能敏感的代碼):
const obj = {};
const proto = { x: 10 };
Object.setPrototypeOf(obj, proto);
檢查構造函數的prototype
是否出現在對象的原型鏈上:
function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype);
const child = new Child();
console.log(child instanceof Parent); // true
檢查對象是否存在于另一個對象的原型鏈上:
const proto = {};
const obj = Object.create(proto);
console.log(proto.isPrototypeOf(obj)); // true
避免意外修改原生原型:
// 危險操作!
Array.prototype.push = function() {
console.log('Array push modified!');
};
// 更安全的擴展方式
function MyArray() {}
MyArray.prototype = Object.create(Array.prototype);
const logSymbol = Symbol('log');
class MyClass {
[logSymbol]() {
console.log('Private method');
}
}
通過mixin模式實現:
const Serializable = {
serialize() {
return JSON.stringify(this);
}
};
const Area = {
getArea() {
return this.length * this.width;
}
};
function mixin(...mixins) {
return function(target) {
Object.assign(target.prototype, ...mixins);
};
}
@mixin(Serializable, Area)
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
Object.create(null)
創建純凈字典對象[[Prototype]]
指向構造函數的prototype
this
綁定到新對象并執行構造函數function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype);
const result = Constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}
所有原型鏈的終點是Object.prototype.__proto__
,即null
console.log(Object.prototype.__proto__); // null
JavaScript的原型機制是其面向對象編程的基石。從ES5的各種繼承模式到ES6的類語法糖,理解原型鏈可以幫助開發者編寫更優雅、高效的代碼。隨著JavaScript語言的發展,雖然類語法讓傳統OOP開發者更易上手,但其底層仍然是基于原型的實現。掌握這些核心概念,方能真正理解JavaScript的設計哲學。
字數統計:約5450字(實際字數可能因排版略有差異) “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。