一篇文章教你JS函式繼承
目錄
- 一. 前言:
- 二.原型鏈繼承:
- 三.借用建構函式繼承(物件偽裝):
- 四.組合繼承:
- 五.寄生組合繼承:
- 六.class繼承:
- 七.總結:
一. 前言:
Hello,大家最近過得好嗎,😃。函式繼承是在裡比較基礎也是比較重要的一部分,而且也是面試中常常要問到的。下面帶你快速瞭解JS中有哪幾種是經常出現且必須掌握的繼承方式。掌握下面的內容面試也差不多沒問題啦~
當然,這需要一定的原型鏈基礎,對原型鏈不熟悉的可以看我這篇文章👉:速識js原型鏈。
二.原型鏈繼承:
原型鏈繼承的要點在於父類的例項作為子類的原型。直接看下面這個例子:
// 父函式 Person function Person(name,age) { // 定義一些屬性 this.name = name;this.age = age; this.nature = ["auroras","wind","moon"]; } // 定義Person原型上的一個方法 Person.prototype.sayLove = function () { console.log(this.name + " like " + this.nature[0]); }; // 子函式 Jack functi客棧on Jack() {} // 父類的例項作為子類的原型 (-------------------實現核心--------------------------) Jack.prototype = new Person();
現在我們建立兩個Jack 的例項,測試看是否實現了繼承Person:
var jack1 = new Jack(); var jack2 = new Jack(); jack2.nature[0] = "sea"; jack1.sayLove(); jack2.sayLove(); console.log(jack1.nature); console.log(jack2.nature);
看執行結果確實繼承了,能執行sayLove方法。但有甚多缺點,建立Jack例項的時候傳遞不了引數name和age,而且不同例項間nature引用型別屬性相互影響,一個改變那都改變:
三.借用建構函式繼承(物件偽裝):
核心在於“盜用建構函式”(constructor stealing)。在子類建構函式中呼叫父類建構函式。因為畢竟函式就是在特定上下文中執行程式碼的簡單物件,所以可以使用apply()和call()方法以新建立的物件為上下文執行建構函式。它能解決原型鏈繼承中傳引數和引用型別屬性XqGbCNH衝突。還是直接看例子:
// 父函式 Person function Person(name,age) { // 定義一些屬性 this.name = name; this.age = age; this.nature = ["auroras","moon"]; } // 定義Person原型上的一個方法 Person.prototype.sayLove = function () { console.log(this.name + " like " + this.nature[0]); }; // 子函式 Lucy function Lucy(name,age) { // 通過call把this指向Lucy,相當於拷貝了一份父函式 Person 裡的內容(---------實現核心--------------) Person.call(this,name,age); } //給子函式原型上也定義一個方法 Lucy.prototype.syaName = function () { console.log("My name is " + this.name); };
現在我們建立兩個Lucy 的例項,測試看是否實現了繼承Person:
var lucy1 = new Lucy("lucy1","20"); var lucy2 = new Lucy("lucy2","22"); lucy2.nature[0] = "sea"; console.log(lucy1.name); console.log(lucy1.nature); console.log(lucy2.nature); lucy1.syaName(); lucy2.syaName(); lucy1.sayLove();
結果看可以繼承了,能傳引數,引用型別屬性也不互相影響,但是缺點顯而易見,可以看到報錯,無法使用父類的原型上的方法sayLove。
四.組合繼承:
組合繼承就是結合了原型鏈繼承和借用建構函式繼承兩者的核心實現的一種繼承方法,既能傳遞引數,引用型別屬性也互不影響,同時子類也能獲取得到父類的方法。這也是目前比較常用的繼承方式。直接看例子:
// 父函式 Person
function Person(name,"moon"];
}
/www.cppcns.com/ 定義Person原型上的一個方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
};
// 子函式Lisa
function Lisa(name,age) {
// 通過call把this指向Lisa,相當於拷貝了一份父函式 Person 裡的內容(------實現核心-----------)
Person.call(this,age);
}
// 父類的例項作為子類的原型 (--------------實現核心-------------------)
Lisa.prototype = new Person();
//小知識點,這裡是讓Lisa的constructor重新指向Lisa,不然因為Lisa的原型為Person例項,constructor會指向Person
Lisa.prototype.constructor = Lisa;
現在我們建立兩個Lisa 的例項,測試看是否實現了繼承Person:
var lisa1 = new Lisa("lisa1","20"); var lisa2 = new Lisa("lisa2","21"); lisa2.nature[0] = "sea"; console.log(lisa1.name); console.log(lisa1.nature); console.log(lisa2.nature); lisa1.sayLove(); lisa2.sayLove();
可以看到基本上實現了我們繼承的功能。也修補了原型鏈和借用建構函式繼承的缺點。但是呢,它還是有一個小缺點,就是可以看到在程式碼註釋實現核心那,兩次都呼叫了Person,那麼Lisa原型上和例項上有了兩份相同的屬性,那就會多少有一些效能浪費。
五.寄生組合繼承:
其實寄生組合繼承和組合繼承差不多的,就是多了一個解決組合繼承上原型和例項產生兩份相同屬性的缺點。解決核心是我們既然只是想要子類原型賦值為父類原型,那沒必要new一個父類例項。直接創造一個新物件,它值為父類的原型,再將它賦值給子類原型就行了。
其中用到Object.create(proto,[propertiesObject])這個方法建立一個新物件。相當於新物件的__proto__為其引數proto。當然Object.create可能低版本ie沒有,所以下面也自定義封裝了Object.create方法,當然只是簡單封裝。直接看例子:
// 父函式 Person function Person(name,"moon"]; } // 定義Person原型上的一個方法 Person.prototype.sayLove = function () { console.log(this.name + " like " + this.nature[0]); }; // 子函式 Andy function Andy(name,age) { Person.call(this,age); } // 如果沒有 Object.create()方法,簡單封裝下 if (!Object.create) { Object.create = function (proto) { function Temp() {} Temp.prototype = proto; return new Temp(); }; } // 呼叫Object.create方法,新建一對像,其__proto__為Person.prototype,並賦值給 Andy.prototype (-------實現核心----------) Andy.prototype = Object.create(Person.prototype); //修改constructor指向 Andy.prototype.constructor = Andy;
現在我們建立兩個Andy的例項,測試看是否實現了繼承Person:
console.log(Andy.prototype.__proto__ === Person.prototype); var andy1 = new Andy("andy1","20"); var andy2 = new Andy("andy2","21"); andy2.nature[0] = "sea"; console.log(andy1.name); console.log(andy1.nature); console.log(andy2.nature); andy1.sayLove(); andy2.sayLove();
完美執行:
六.class繼承:
ES6出了class語法糖之後,就可以通過class定義類並實現類的繼承。直接看例子:
//定義一個父類 Animal class Animal { //這裡constructor指向類本身,跟es5行為一樣的 constructor(name) { this.name = name; } likeEat() { console.log(this.name + " like eat " + this.food); } } //定義一個子類 Dog ,通過 extends 繼承父類Animal class Dog extends Animal { constructor(name,food) { //通過super(屬性名)繼承父類屬性 super(name); this.food = food; } likeEat() { //通過super.+父類方法 實現繼承父類方法 super.likeEat(); } }
new一個Dog例項,測試看看,Dog是否繼承了Animal:
var jinmao = new Dog("jinmao","bone"); console.log(jinmao.name); jinmao.likeEat();
可以看到完美實現了:
七.總結:
方法 | 優點 | 缺點 |
---|---|---|
原型鏈繼承 | 能繼承父原型上屬性方法等等… | 無法傳參、引用型別屬性衝突等等… |
借用建構函式繼承 | 可以傳參,引用型別屬性不衝突等等… | 無法繼承父原型上方法等等… |
組合繼承 | 有上面兩種的優點,並解決其缺點 | 呼叫兩次父例項產生兩份相同屬性等等… |
寄生組合繼承 | 有上面三種優點,並解決其缺點 | 可能不太直觀等等… |
class繼承 | es6新語法,簡潔直觀等等… | 低版本ie不支援es6等等… |
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注我們的更多內容!