JS進階系列 --- 繼承
轉載自:一篇文章理解JS繼承——原型鏈/建構函式/組合/原型式/寄生式/寄生組合/Class extends
同時加入了我個人的一些例子和淺見
繼承分類
先來個整體印象。如圖所示,JS中繼承可以按照是否使用object函式(在下文中會提到),將繼承分成兩部分(Object.create是ES5新增的方法,用來規範化這個函式)。
其中,原型鏈繼承和原型式繼承有一樣的優缺點,建構函式繼承與寄生式繼承也相互對應。寄生組合繼承基於Object.create, 同時優化了組合繼承,成為了完美的繼承方式。ES6 Class Extends的結果與寄生組合繼承基本一致,但是實現方案又略有不同。
繼承方式
1. 原型鏈繼承
SubType.prototype = new SuperType()
/*所有涉及到原型鏈繼承的繼承方式都要修改子類建構函式的指向,否則子類例項的建構函式會指向SuperType*/
SubType.prototype.constructor = SubType;
核心:將父類的例項作為子類的原型。
優點:父類方法可以複用。
缺點:
- 父類的引用屬性會被所有子類例項共享
- 子類構建例項時不能向父類傳遞引數
例子:
function Animal(){};
Animal.prototype.say = function(){console.log("動物叫聲!")}
function Dog(){};
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {console.log("汪汪汪!");}
var wc = new Dog();
記憶體圖分析
2. 建構函式繼承
核心:將父類建構函式的內容複製給了子類的建構函式。這是所有繼承中唯一一個不涉及到prototype的繼承。
SuperType.call(SubType);
優點:和原型鏈繼承完全反過來
- 父類的引用屬性不會被共享
- 子類構建例項時可以向父類傳遞引數
缺點:父類的方法不能複用,子類例項的方法每次都是單獨建立的。
例子:
function Animal(name){this.name = name};
Animal.prototype.say = function(){console.log("動物叫聲!")}
function Dog(name,age){Animal.call(this,name);this.age=age;};
Dog.prototype.bark = function () {console.log("汪汪汪!");}
var wc = new Dog("旺財",3);
記憶體圖分析
3. 組合繼承
核心:原型鏈繼承和建構函式繼承的組合,兼具了二者的優點。
優點:
- 父類的方法可以被複用
- 父類的引用屬性不會被共享
- 子類構建例項時可以向父類傳遞引數
缺點:呼叫了兩次父類的建構函式,第一次給子類的原型新增了父類的屬性,第二次又給子類的建構函式新增了父類的屬性,從而覆蓋了子類原型中的同名引數。這種被覆蓋的情況造成了效能上的浪費。
例子:
function Animal(name){this.name=name};
Animal.prototype.say = function(){console.log("動物叫聲!")}
function Dog(name,age){Animal.call(this,name);this.age=age;}; //這裡把name複製了一份給this
Dog.prototype = new Animal(); //這裡把name複製了一份給Dog.prototype
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {console.log("汪汪汪!");}
var wc = new Dog("旺財",3);
記憶體圖分析
4. 原型式繼承
核心:原型式繼承的object方法本質上是對引數物件的一個淺複製。
優點:父類方法可以複用。
缺點:
- 父類的引用屬性會被所有子類例項共享
- 子類構建例項時不能向父類傳遞引數
例子:
function createAnimal(o) {
function Animal(){}
Animal.prototype = o;
return new Animal();
}
var animal = {
name: "動物",
kinds: ["貓","狗","雞"]
};
var dog = createAnimal(animal);
var cat = createAnimal(animal);
記憶體圖分析
ECMAScript 5 通過新增
Object.create()
方法規範化了原型式繼承。這個方法接收兩個引數:一個用作新物件原型的物件;一個(可選的)為新物件定義額外屬性的物件。在傳入一個引數的情況下,Object.create()
與object()
方法的行為相同。——《JAVASCript高階程式設計》
所以上文中程式碼可以轉變為:
var dog = object(animal); => var dog = Object.create(animal);
5. 寄生式繼承
核心:使用原型式繼承獲得一個目標物件的淺複製,然後增強這個淺複製的能力。
優缺點:僅提供一種思路,沒什麼優點。
例子:dog&cat 寄生式繼承 animal
function createAnimal(o) {
var clone = Object.create(o);
clone.say = function(){
console.log("動物叫聲");
}
return clone;
}
var animal = {
name: "動物",
kinds: ["貓","狗","雞"]
};
var dog = createAnimal(animal);
var cat = createAnimal(animal);
記憶體圖分析
6. 寄生組合繼承
核心:組合繼承有一個會兩次呼叫父類的建構函式造成浪費的缺點,寄生組合繼承就可以解決這個問題。
原理:
因為組合繼承是中
Dog.prototype = new Animal
,Dog的原型經過了一次Animal的new操作
而寄生組合繼承中var prototype = Object.create(Animal.prototype);
在create函式內部,雖然做了一次new操作,但不是Animal的new操作,只是把new出來物件的prototype指向了Animal.prototype,並把這個物件的地址賦值給了prototype
優缺點:這是一種完美的繼承方式。
例子:
function createAnimal(Obj,Animal) {
var prototype = Object.create(Animal.prototype);
prototype.constructor = Obj;
Obj.prototype = prototype;
}
function Animal(name){this.name = name;this.colors = ["red","blue"];}
Animal.prototype.say = function(){console.log("動物叫聲!")}
function Dog(name, age){Animal.call(this, name);this.age = age;}
createAnimal(Dog, Animal);
Dog.prototype.bark = function(){console.log("汪汪汪!")};
var dog = new Dog("小黃",3);
記憶體圖分析
7. ES6 Class extends
核心: ES6繼承的結果和寄生組合繼承相似,本質上,ES6繼承是一種語法糖。但是,寄生組合繼承是先建立子類例項this物件,然後再對其增強;而ES6先將父類例項物件的屬性和方法,加到this上面(所以必須先呼叫super方法),然後再用子類的建構函式修改this。
class A {}
class B extends A {
constructor() {
super();
}
}
原理:
class A {
}
class B {
}
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
// B 的例項繼承 A 的例項
Object.setPrototypeOf(B.prototype, A.prototype);
// B 繼承 A 的靜態屬性
Object.setPrototypeOf(B, A);
ES6繼承與ES5繼承的異同:
相同點:本質上ES6繼承是ES5繼承的語法糖
不同點:
- ES6繼承中子類的建構函式的原型鏈指向父類的建構函式,ES5中使用的是建構函式複製,沒有原型鏈指向。
- ES6子類例項的構建,基於父類例項,ES5中不是。
總結
- ES6 Class extends是ES5繼承的語法糖
- JS的繼承除了建構函式繼承之外都基於原型鏈構建的
- 可以用寄生組合繼承實現ES6 Class extends,但是還是會有細微的差別
相關文章
- JS進階(3):人人都能懂的繼承JS繼承
- JavaScript進階之繼承JavaScript繼承
- 重學 JS 系列:聊聊繼承JS繼承
- js之繼承JS繼承
- js繼承方式JS繼承
- Web前端------JS高階繼承的實現方式Web前端JS繼承
- JS原型鏈繼承JS原型繼承
- JS 相容、繼承、bind、thisJS繼承
- JS中的繼承JS繼承
- js 繼承小結JS繼承
- js繼承圖解JS繼承圖解
- 【廖雪峰python進階筆記】類的繼承Python筆記繼承
- 「MoreThanJava」Day 5:物件導向進階——繼承詳解Java物件繼承
- 淺談JS的繼承JS繼承
- 初步瞭解 JS 繼承JS繼承
- JS中的繼承(下)JS繼承
- js繼承方式講解JS繼承
- 聊聊JS中的繼承JS繼承
- JS專題之繼承JS繼承
- JS中的繼承(上)JS繼承
- js的13種繼承JS繼承
- 圖解js的繼承圖解JS繼承
- odoo 繼承(owl繼承、web繼承、view繼承)Odoo繼承WebView
- 【進階5-2期】圖解原型鏈及其繼承圖解原型繼承
- C++高階教程之繼承得本質:單繼承(一)C++繼承
- JS中繼承方式總結JS中繼繼承
- JS中繼承的實現JS中繼繼承
- 小議JS原型鏈、繼承JS原型繼承
- ? 一文看懂 JS 繼承JS繼承
- JS的原型鏈和繼承JS原型繼承
- JS 繼承的正確操作JS繼承
- Js繼承之聖盃模式JS繼承模式
- js 組合繼承詳解JS繼承
- js--如何實現繼承?JS繼承
- JS物件導向:JS繼承方法總結JS物件繼承
- python高階語法:繼承性Python繼承
- JS進階系列-JS執行期上下文(一)JS
- 徹底弄懂JS原型與繼承JS原型繼承