摘要:使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承??偨Y實現繼承有種方式原型鏈繼承借用構造函數繼承組合繼承原型式繼承寄生式繼承寄生組合式繼承寄生組合式繼承是大家公認的最好的實現引用類型繼承的方法。
簡介
本文不準備深入細節,主要是對《JavaScript高級程序設計中》介紹的JS如何實現繼承做一個總結,畢竟好記性不如爛筆頭。文末會附帶一張神圖,搞清楚這張圖,原型鏈也就沒有什么問題了。
ES5實現繼承的六種方式 1. 原型鏈基本思想:
利用原型鏈讓一個引用類型繼承另一個引用類型的屬性和方法。
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; }; // 子類 SubType function SubType () { this.subProperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subProperty; }; // 實例 var instance = new SubType(); console.log(instance); console.log(instance.getSuperValue()); // true console.log(instance instanceof SubType); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof Object); // true console.log(SubType.prototype.isPrototypeOf(instance)); // true console.log(SuperType.prototype.isPrototypeOf(instance)); // true console.log(Object.prototype.isPrototypeOf(instance)); // true
缺點:
1. 來自原型對象的引用屬性是所有實例共享的。
2. 創建子類實例時,無法向父類構造函數傳參。
舉例如下:
// 1. 來自原型對象的引用屬性是所有實例共享的 // 父類 function SuperType () { this.colors = ["red", "blue", "green"]; } // 子類 function SubType () { } SubType.prototype = new SuperType(); // 實例 var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType(); console.log(instance2.colors); // ["red", "blue", "green", "black"] // 因為修改colors是修改的SubType.prototype.colors,所以所有的實例都會更新
// 2. 創建子類實例時,無法向父類構造函數傳參 // 調用父類是在 SubType.prototype = new SuperType() // 新建子類實例調用 new SubType() // 所以無法再new SubType() 的時候給父類 SuperType() 傳參2. 借用構造函數
基本思想:
在子類構造函數的內部通過call()以及apply()調用父類構造函數。
// 父類 SuperType function SuperType (name) { this.name = name; this.colors = ["red", "blue", "green"]; this.getName = function () { return this.name; } } // 子類 function SubType (name) { // 繼承了SuperType,同時還傳遞了參數 SuperType.call(this, name); // 實例屬性 this.age = 20; } // 實例 var instance1 = new SubType("Tom"); instance1.colors.push("black"); console.log(instance1.name); // "Tom" console.log(instance1.getName()); // "Tom" console.log(instance1.age); // 20 console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType("Peter"); console.log(instance2.name); // "Peter" console.log(instance2.getName()); // "Peter" console.log(instance2.age); // 20 console.log(instance2.colors); // ["red", "blue", "green"]
可以看到,借用構造函數實現繼承,解決了原型鏈繼承的兩個問題,既可以在新建子類實例的時候給父類構造函數傳遞參數,也不會造成子類實例共享父類引用變量。
但是你注意到了嗎,這里我們把父類方法也寫在了SuperType()構造函數里面,可以像前面一樣寫在SuperType.prototype上嗎?
答案是不可以,必須寫在SuperType()構造函數里面。因為這里是通過調用SuperType.call(this)來實現繼承的,并沒有通過new生成一個父類實例,所以如果寫在prototype上,子類是無法拿到的。
缺點:
1. 如果方法都在構造函數中定義,那么就無法復用函數。每次構建實例時都會在實例中保留方法函數,造成了內存的浪費,同時也無法實現同步更新,因為每個實例都是多帶帶的方法函數。如果方法寫在prototype上,就只會有一份,更新時候會做到同步更新。
3. 組合繼承基本思想:
將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。
使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。
// 父類 function SuperType (name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); } // 子類 function SubType (name, age) { // 繼承父類實例屬性 SuperType.call(this, name); // 子類實例屬性 this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); }; // 實例 var instance1 = new SubType("Tom", 20); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "green", "black"] instance1.sayName(); // "Tom" instance1.sayAge(); // 20 var instance2 = new SubType("Peter", 30); console.log(instance2.colors); // ["red", "blue", "green"] instance2.sayName(); // "Peter" instance2.sayAge(); // 30
缺點:
1. 調用了兩次父類構造函數,一次通過SuperType.call(this)調用,一次通過new SuperType()調用。
4. 原型式繼承基本思想:
不使用嚴格意義上的構造函數,借助原型可以基于已有的對象創建新的對象,同時還不必因此創建自定義類型。
// 在object函數內部,先創建了一個臨時的構造函數,然后將傳入的對象作為這個構造函數的原型,最后返回這個臨時類型的一個新實例。 // 從本質上講,object()對傳入其中的對象執行了一次淺復制。 function object (o) { function F() {} F.prototype = o; return new F(); } var person = { name: "Tom", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(anotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(yetAnotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
ECMAScript5中新增了一個方法Object.create(prototype, descripter)接收兩個參數:
prototype(必選),用作新對象的原型對象
descripter(可選),為新對象定義額外屬性的對象
在傳入一個參數的情況下,Object.create()與前面寫的object()方法的行為相同。
var person = { name: "Tom", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person, { name: { value: "Linda", enumerable: true } }); yetAnotherPerson.friends.push("Barbie"); console.log(anotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(yetAnotherPerson.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"] console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
缺點:
1. 和原型鏈繼承一樣,所有子類實例共享父類的引用類型。
5. 寄生式繼承基本原理:
寄生式繼承是與原型式繼承緊密相關的一種思路,創建一個僅用于封裝繼承過程的函數,該函數內部以某種形式來做增強對象,最后返回對象。
function object (o) { function F() {} F.prototype = o; return new F(); } function createAnother (o) { var clone = object(o); clone.sayHi = function () { console.log("Hi"); } return clone; } var person = { name: "Tom", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "Hi" anotherPerson.friends.push("Rob"); console.log(anotherPerson.friends); // ["Shelby", "Court", "Van", "Rob"] var yerAnotherPerson = createAnother(person); console.log(yerAnotherPerson.friends); // ["Shelby", "Court", "Van", "Rob"]
缺點:
1. 和原型鏈式繼承一樣,所有子類實例共享父類引用類型。
2. 和借用構造函數繼承一樣,每次創建對象都會創建一次方法。
6. 寄生組合式繼承基本思想:
將寄生式繼承和組合繼承相結合,解決了組合式繼承中會調用兩次父類構造函數的缺點。
組合繼承是JavaScript最常用的繼承模式,它最大的問題就是無論在什么情況下,都會調用兩次父類構造函數:一次是在創建子類原型的時候,另一次是在子類構造函數內部。
// 組合繼承 function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { alert(this.name); }; function SubType(name, age) { SuperType.call(this, name); //第二次調用 SuperType() this.age = age; } SubType.prototype = new SuperType(); //第一次調用 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { alert(this.age); };
組合繼承在第一次調用SuperType構造函數時,SubType.prototype會得到兩個屬性:name和colors;它們都是 SuperType 的實例屬性,只不過現在位于 SubType的原型中。當調用SubType構造函數時,又會調用一次SuperType構造函數,這一次又在新對象上創建了實例屬性name和colors。于是,這兩個屬性就屏蔽了原型中的兩個同名屬性。
所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。
其背后的基本思路是:不必為了指定子類型的原型而調用父類的構造函數,我們需要的無非就是父類原型的一個副本而已。本質上,就是使用寄生式繼承來繼承父類的prototype,然后再將結果指定給子類的prototype。
寄生組合式繼承的基本模型如下:
function inheritPrototype(SubType, SuperType) { var prototype = object(SuperType.prototype); // 創建對象 prototype.constructor = SubType; // 增強對象 SubType.prototype = prototype; // 指定對象 }
實現一個完整的寄生組合式繼承:
function object(o) { function F() { } F.prototype = o; return new F(); } function inheritPrototype(SubType, SuperType) { var prototype = object(SuperType.prototype); // 創建對象 prototype.constructor = SubType; // 增強對象 SubType.prototype = prototype; // 指定對象 } // 父類 function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; // 子類 function SubType(name, age) { // 繼承父類實例屬性 SuperType.call(this, name); // 子類實例屬性 this.age = age; } // 繼承父類方法 inheritPrototype(SubType, SuperType); // 子類方法 SubType.prototype.sayAge = function () { console.log(this.age); }; // 實例 var instance1 = new SubType("Tom", 20); instance1.colors.push("black"); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType("Peter", 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"]
寄生組合式繼承的高效率體現在它只調用了一次SuperType構造函數,并且因此避免了再SubType.prototype上面創建不必要的、多余的屬性。與此同時,原型鏈還能保持不變。因此,還能夠正常使用instanceof和isPrototypeOf()。
開發人員普遍認為寄生組合式繼承是引用類型最理想的繼承方式。
ES6實現繼承// 父類 class SuperType { constructor(name) { this.name = name; this.colors = ["red", "blue", "green"]; } sayName() { console.log(this.name); }; } // 子類 class SubType extends SuperType { constructor(name, age) { // 繼承父類實例屬性和prototype上的方法 super(name); // 子類實例屬性 this.age = age; } // 子類方法 sayAge() { console.log(this.age); } } // 實例 var instance1 = new SubType("Tom", 20); instance1.colors.push("black"); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType("Peter", 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"]
用ES6的語法來實現繼承非常的簡單,下面是把這段代碼放到Babel里轉碼的結果圖片:
可以看到,底層其實也是用寄生組合式繼承來實現的。
總結ES5實現繼承有6種方式:
原型鏈繼承
借用構造函數繼承
組合繼承
原型式繼承
寄生式繼承
寄生組合式繼承
寄生組合式繼承是大家公認的最好的實現引用類型繼承的方法。
ES6新增class和extends語法,用來定義類和實現繼承,底層也是采用了寄生組合式繼承。
附圖:
歡迎關注我的公眾號文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97151.html
摘要:中的繼承并不是明確規定的,而是通過模仿實現的。繼承中的繼承又稱模擬類繼承。將函數抽離到全局對象中,函數內部直接通過作用域鏈查找函數。這種范式編程是基于作用域鏈,與前面講的繼承是基于原型鏈的本質區別是屬性查找方式的不同。 這一節梳理對象的繼承。 我們主要使用繼承來實現代碼的抽象和代碼的復用,在應用層實現功能的封裝。 javascript 的對象繼承方式真的是百花齊放,屬性繼承、原型繼承、...
摘要:和構造函數前面提到,是個內置隱藏屬性,雖然在可以通過訪問,但是其設計本意是不可被讀取和修改的,那么我們如何利用原型鏈來建立繼承關系提供了關鍵字。到這兒,思路就清晰了,怎么讓對象和對象的相連實現繼承只需把的構造函數的連接到就行了。 什么是繼承? 大多數人使用繼承不外乎是為了獲得這兩點好處,代碼的抽象和代碼的復用。代碼的抽象就不用說了,交通工具和汽車這類的例子數不勝數,在傳統的OO語言中(...
摘要:首先,需要來理清一些基礎的計算機編程概念編程哲學與設計模式計算機編程理念源自于對現實抽象的哲學思考,面向對象編程是其一種思維方式,與它并駕齊驅的是另外兩種思路過程式和函數式編程。 JavaScript 中的原型機制一直以來都被眾多開發者(包括本人)低估甚至忽視了,這是因為絕大多數人沒有想要深刻理解這個機制的內涵,以及越來越多的開發者缺乏計算機編程相關的基礎知識。對于這樣的開發者來說 J...
摘要:前言作為中最重要的內容之一,繼承問題一直是我們關注的重點。如果一個類別繼承自另一個類別,就把這個稱為的子類,而把稱為的父類別也可以稱是的超類。 前言 作為 JavaScript 中最重要的內容之一,繼承問題一直是我們關注的重點。那么你是否清晰地知道它的原理以及各種實現方式呢 閱讀這篇文章,你將知道: 什么是繼承 實現繼承有哪幾種方式 它們各有什么特點 這里默認你已經清楚的知道構造函...
摘要:繼承前言作為一門輕量級的腳本語言在和的橫空出世之后將其推向的新的高度雖然中出現的新的生成對象的類語法格式但依然為的語法糖而我們依然有必要從的原生實現入手來了解它的繼承實現方式給出了更加簡潔的固定的類聲明方式有興趣的可以查看阮一峰的入門下面給 javascript繼承 前言 javascript作為一門輕量級的腳本語言在ES6和node.js的橫空出世之后將其推向的新的高度,雖然 ES6...
閱讀 2910·2023-04-26 02:14
閱讀 3760·2019-08-30 15:55
閱讀 1847·2019-08-29 16:42
閱讀 2762·2019-08-26 11:55
閱讀 2851·2019-08-23 13:38
閱讀 489·2019-08-23 12:10
閱讀 1317·2019-08-23 11:44
閱讀 2807·2019-08-23 11:43