核心:將父類的實例做爲子類的原型javascript
首先,要知道構造函數、原型和實例之間的關係:構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個原型對象的指針。前端
function Father(){
this.name = '父類的名字';
}
Father.prototype.getFatherName = function(){
console.log('父類的方法');
}
function Son(){
this.name = '子類的名字';
}
// 若是此時有Son的原型對象有方法或屬性,下面Son.prototype = new Father(),因爲原型重定向,原型上的方法和屬性會丟失
Son.prototype.getAge = function(){
console.log('子類的年齡')
}
Son.prototype = new Father(); // 核心:建立父類的實例,並將該實例賦值給子類的prototype
Son.prototype.getSonName = function(){
console.log('子類的方法');
}
var son = new Son();
son.getFatherName(); // 父類的方法
Son.prototype.__proto__.getFatherName = function(){ // 缺點:若是有多個實例對其父類原型,則會互相影響
console.log('子類改變父類的方法');
}
son.getFatherName(); // 子類改變父類的方法
複製代碼
缺點:java
父類使用this聲明的屬性(私有屬性和公有屬性)被全部實例共享,在多個實例之間對引用類型數據操做會互相影響。後端
建立子類實例時,沒法向父類構造函數傳參。函數
核心:使用父類的構造函數來加強子類實例,即複製父類的實例屬性給子類性能
function Father(name, age){
this.name = name;
this.age = age;
}
Father.prototype.getFatherName = function(){
console.log('父類的方法');
}
function Son(name, age, job){
Father.call(this,name, age); // 繼承自Father
this.job = job;
}
var son = new Son('jacky', 22, '前端開發');
//son.getFatherName(); // Uncaught TypeError: son.getFatherName is not a function
複製代碼
優勢:ui
缺點:this
核心:組合上述兩種方法,用原型鏈實現對原型屬性和方法的繼承,用借用構造函數技術來實現實例屬性的繼承。spa
function Father(name, age){
this.name = name;
this.age = age;
this.sex = 'man';
}
Father.prototype.getFatherName = function(){
console.log('父類的方法')
}
function Son(name, age, job){
Father.call(this,name,age); // 第二次調用:建立子類型實例的時候
this.job = job;
}
Son.prototype = new Father(); // 第一次調用:設置子類型實例的原型的時候
Son.prototype.constructor = Son; // prototype構造器指回本身
var son = new Son('jacky', 22, '前端開發');
son.getFatherName();
console.log(son)
複製代碼
優勢:prototype
缺點:
拓展:
返回建立實例對象的Object構造函數的引用。
當咱們只有實例對象沒有構造函數的引用時: 某些場景下,咱們對實例對象通過多輪導入導出,咱們不知道實例是從哪一個函數中構造出來或者追蹤實例的構造函數,較爲艱難。(它主要防止一種狀況下出錯,就是你顯式地去使用構造函數。好比,我並不知道instance是由哪一個函數實例化出來的,可是我想clone一個,這時就能夠這樣——>instance.constructor) 這個時候就能夠經過實例對象的constructor屬性來獲得構造函數的引用
let instance = new sonFn() // 實例化子類
export instance;
// 多輪導入+導出,致使sonFn追蹤很是麻煩,或者不想在文件中再引入sonFn
let fn = instance.constructor
複製代碼
所以每次重寫函數的prototype都應該修正一下constructor的指向,以保持讀取constructor指向的一致性
核心:利用一個空對象做爲中介,將某個對象直接賦值給空對象構造函數的原型,而後返回這個函數的調用,這個函數就變成了個能夠隨意增添屬性的實例或對象。
/* Object.create() 的實現原理 */
// cloneObject()對傳入其中的對象執行了一次淺拷貝,將構造函數F的原型直接指向傳入的對象。
function cloneObject(obj){
function F(){}
F.prototype = obj; // 將傳進來obj對象做爲空函數的prototype
return new F(); // 此對象的原型爲被繼承的對象, 經過原型鏈查找能夠拿到被繼承對象的屬性
}
var father = {
name: 'jacky',
age: 22,
courses: ['前端']
}
// var son1 = Object.create(father); // 效果同樣
var son1 = cloneObject(father);
son1.courses.push('後端');
var son2 = cloneObject(father);
son2.courses.push('全棧');
console.log(father.courses); // ["前端", "後端", "全棧"]
複製代碼
優勢:
從已有對象衍生新對象,不須要建立自定義類型
缺點:
與原型鏈繼承同樣。多個實例共享被繼承對象的屬性,存在篡改的可能;也沒法傳參。
核心:在原型式繼承的基礎上,建立一個僅用於封裝繼承過程的函數,該函數在內部以某種形式來作加強對象(增長了一些新的方法和屬性),最後返回對象。
使用場景:專門爲對象來作某種固定方式的加強。
function createAnother(obj){
var clone = Object.create(obj);
clone.skill = function(){ // 以某種方式來加強這個對象
console.log('run');
};
return clone;
}
var animal = {
eat: 'food',
drink: 'water'
}
var dog = createAnother(animal);
dog.skill();
複製代碼
優勢:沒有建立自定義類型,由於只是套了個殼子增長特定屬性/方法返回對象,以達到加強對象的目的
缺點:
同原型式繼承:原型鏈繼承多個實例的引用類型屬性指向相同,存在篡改的可能,也沒法傳遞參數
核心:結合借用構造函數傳遞參數和寄生模式實現繼承
function Father(name, age){
this.name = name;
this.age = age;
}
Father.prototype.getFatherName = function(){
console.log('父類的方法')
}
function Son(name, age, job){
Father.call(this,name,age); // 借用構造繼承: 繼承父類經過this聲明屬性和方法至子類實例的屬性上
this.job = job;
}
// 寄生式繼承:封裝了son.prototype對象原型式繼承father.prototype的過程,而且加強了傳入的對象。
function inheritPrototype(son,father){
var clone = Object.create(father.prototype); // 原型式繼承:淺拷貝father.prototype對象
clone.constructor = son; // 加強對象,彌補因重寫原型而失去的默認的constructor 屬性
son.prototype = clone; // 指定對象,將新建立的對象賦值給子類的原型
}
inheritPrototype(Son,Father); // 將父類原型指向子類
// 新增子類原型屬性
Son.prototype.getSonName = function(){
console.log('子類的方法')
}
var son = new Son('jacky',22,'前端開發');
console.log(son);
複製代碼
缺點:
硬要說的話,就是給子類原型添加屬性和方法的時候,必定要放在inheritPrototype()方法以後
核心: 類之間經過extends關鍵字實現繼承,清晰方便。 class 僅僅是一個語法糖,它的核心思想仍然是寄生組合式繼承。
class Father {
constructor(name, age) {
this.name = name;
this.age = age;
}
skill() {
console.log('父類的技能');
}
}
class Son extends Father {
constructor(name, age, job){
super(name, age); // 調用父類的constructor,只有調用super以後,纔可使用this關鍵字
this.job = job;
}
getInfo() {
console.log(this.name, this.age, this.job);
}
}
let son = new Son('jacky',22,'前端開發');
son.skill(); // 父類的技能
son.getInfo(); // jacky 22 前端開發
複製代碼
子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類本身的this對象,必須先經過父類的構造函數完成塑造,獲得與父類一樣的實例屬性和方法,而後再對其進行加工,加上子類本身的實例屬性和方法。若是不調用super方法,子類就得不到this對象。