?
“天天開心就好”
先來講講概念:?
原型(Prototype)
什么是原型?
原型是 JavaScript 中實現對象間共享屬性和方法的機制。每個 JavaScript 對象(除了?null
)都有一個內部鏈接指向另一個對象,這個對象就是它的"原型"(prototype)。
繼承(Inheritance)
什么是繼承?
繼承是面向對象編程中的一個核心概念,它允許一個對象(子對象)獲取另一個對象(父對象)的屬性和方法。在 JavaScript 中,繼承主要通過原型鏈來實現。
好的現在我們明確了什么是原型,什么是繼承。簡單來說,原型就是一個機制,每個對象內部都一個內部鏈接指向他的原型。繼承我的理解就是一種行為,就是像繼承財產那樣繼承父對象的屬性和方法,可謂是形容十分貼切。
原型基礎
原型對象 (prototype)
- 每個函數都有一個?
prototype
?屬性(箭頭函數除外) - 這個屬性指向一個對象,稱為"原型對象"
- 原型對象包含可以被特定類型的所有實例共享的屬性和方法
function Person() {}
Person.prototype.name = 'Default';
Person.prototype.sayHello = function() {console.log(`Hello, I'm ${this.name}`);
};
__proto__
?屬性
- 每個對象都有一個?
__proto__
?屬性(現已標準化為?Object.getPrototypeOf()
) - 指向創建該對象的構造函數的原型對象
const person = new Person();
console.log(person.__proto__ === Person.prototype); // true
這個很好理解了,我在這里想用c語言里面的指針來形容了。prototype就像是地址對應的數據,而_proto_就像是指向他的指針。不太恰當哈
?我們經常這樣說:對象的繼承是通過原型鏈實現的。
那么什么是原型鏈:
什么是原型鏈?
原型鏈(Prototype Chain)是 JavaScript 中實現繼承的核心機制。當訪問一個對象的屬性或方法時,JavaScript 引擎會沿著對象的原型鏈向上查找,直到找到該屬性或到達原型鏈的末端(null
)。
原型鏈的構成
- ??每個對象都有一個?
__proto__
?屬性??(現已標準化為?Object.getPrototypeOf()
) - ??每個函數都有一個?
prototype
?屬性?? - ??原型鏈的終點是?
null
??
原型鏈的工作原理
當訪問一個對象的屬性時:
- 首先在對象自身查找該屬性
- 如果沒有找到,則查找對象的?
__proto__
(即其構造函數的?prototype
) - 如果還沒有找到,繼續查找?
__proto__.__proto__
,依此類推 - 直到找到該屬性或到達?
null
(此時返回?undefined
)
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!');
};const myDog = new Dog('Buddy', 'Golden Retriever');// 原型鏈:
// myDog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
我們講原型和繼承就是在意js中繼承這種行為是怎么實現的,就像現實中大家只在乎怎么分遺產一樣!
繼承實現方式
?1.原型鏈繼承
function Parent() {this.parentProperty = true;
}
Parent.prototype.getParentValue = function() {return this.parentProperty;
};function Child() {this.childProperty = false;
}
// 繼承 Parent
Child.prototype = new Parent();const instance = new Child();
console.log(instance.getParentValue()); // true
??問題??:
- 所有子類實例共享同一個父類實例
- 無法向父類構造函數傳參
2. 構造函數繼承
function Parent(name) {this.name = name;
}function Child(name) {Parent.call(this, name); // 在子類構造函數中調用父類構造函數
}const child = new Child('Alice');
console.log(child.name); // 'Alice'
??優點??:
- 可以向父類傳參
- 每個子類實例都有獨立的父類屬性副本
??缺點??:
- 無法繼承父類原型上的方法
3.組合繼承(最常用)
function Parent(name) {this.name = name;
}
Parent.prototype.sayName = function() {console.log(this.name);
};function Child(name, age) {Parent.call(this, name); // 第二次調用 Parentthis.age = age;
}
Child.prototype = new Parent(); // 第一次調用 Parent
Child.prototype.constructor = Child; // 修復構造函數指向const child = new Child('Alice', 25);
child.sayName(); // 'Alice'
??優點??:
- 結合了原型鏈和構造函數的優點
- 既能繼承原型方法,又能保證實例屬性獨立
??缺點??:
- 父類構造函數被調用了兩次
4. 原型式繼承
function object(o) {function F() {}F.prototype = o;return new F();
}const parent = { name: 'Parent' };
const child = object(parent);
console.log(child.name); // 'Parent'
ES5 標準化為?Object.create()
:
const child = Object.create(parent);
5. 寄生式繼承
function createAnother(original) {const clone = Object.create(original);clone.sayHi = function() {console.log('Hi');};return clone;
}
6. 寄生組合式繼承(最佳實踐)
function inheritPrototype(child, parent) {const prototype = Object.create(parent.prototype);prototype.constructor = child;child.prototype = prototype;
}function Parent(name) {this.name = name;
}
Parent.prototype.sayName = function() {console.log(this.name);
};function Child(name, age) {Parent.call(this, name);this.age = age;
}inheritPrototype(Child, Parent);const child = new Child('Alice', 25);
child.sayName(); // 'Alice'
??優點??:
- 只調用一次父類構造函數
- 原型鏈保持正確
- 最理想的繼承方式
ES6 的 class 繼承
class Parent {constructor(name) {this.name = name;}sayName() {console.log(this.name);}
}class Child extends Parent {constructor(name, age) {super(name); // 調用父類構造函數this.age = age;}
}const child = new Child('Alice', 25);
child.sayName(); // 'Alice'
??注意??:
class
?本質上是語法糖,底層仍然是基于原型的繼承extends
?實現了寄生組合式繼承- 必須在使用?
this
?前調用?super()
繼承是js中很核心的機制了,有很多中方式來實現繼承,繼承的好處就是我們可以直接繼承父對象的方法和屬性而不用自己再次定義了。用好繼承可以大大提升我們的代碼水平,幫助我們實現更多復雜需求。?