您好,登錄后才能下訂單哦!
本文全面講述了JS繼承分類、原理與用法。分享給大家供大家參考,具體如下:
許多 OO 語言都支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。由于 ECMAScript 中的函數沒有簽名,所以在 JS 中無法實現接口繼承。ECMAScript 只支持實現繼承,而且其實現繼承主要是依靠原型鏈來實現的。所以,下面所要說的原型鏈繼承、借用構造函數繼承、組合繼承、原型式繼承、寄生式繼承和寄生組合式繼承都屬于實現繼承。
最后的最后,我會解釋 ES6 中的 extend
語法利用的是寄生組合式繼承。
1. 原型鏈繼承
ECMAScript 中描述了原型鏈的概念,并將原型鏈作為實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。實現原型鏈繼承有一種基本模式,其代碼大致如下:
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType(); // 敲黑板!這是重點:繼承了 SuperType SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); // true
原型鏈繼承的一個本質是重寫原型對象,代之以一個新類型的實例;給原型添加方法的代碼一定要放在替換原型的語句之后;在通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。
實例屬性在實例化后,會掛載在實例對象下面,因此稱之為實例屬性。上面的代碼中 SubType.prototype = new SuperType();
,執行完這條語句后,原 SuperType 的實例屬性 property 就掛載在了 SubType.prototype
對象下面。這其實是個隱患,具體原因后面會講到。
每次去查找屬性或方法的時候,在找不到屬性或方法的情況下,搜索過程總是要一環一環的前行到原型鏈末端才會停下來。
所有引用類型默認都繼承了 Object,而這個繼承也是通過原型鏈實現的。由此可知,所有函數的默認原型都是 object 的實例,因此函數的默認原型都會包含一個內部指針,指向 Object.prototype 。
缺點:
* 題外話:確定原型與實例的關系的兩種方式
alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true
2. 借用構造函數繼承
借用構造函數繼承,也叫偽造對象或經典繼承。其基本思想相當簡單,即在子類型構造函數的內部調用超類型構造函數。其繼承代碼大致如下:
function SuperType(){ this.colors = [ "red", "blue", "green"]; } function SubType(){ SuperType.call(this); // 敲黑板!注意了這里繼承了 SuperType } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); // "red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); // "red,blue,green"
通過使用 call()
方法(或 apply()
方法也可以),我們實際上是在(未來將要)新創建的子類的實例環境下調用父類構造函數。
為了確保超類構造函數不會重寫子類型的屬性,可以在調用超類型構造函數后,再添加應該在子類型中定義的屬性。
優點:可以在子類型構造函數中向超類型構造函數傳遞參數。
缺點:
3. 組合繼承
組合繼承(combination inheritance),有時候也叫做偽經典繼承,其背后的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。其繼承代碼大致如下:
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); // 繼承屬性 this.age = age; // 先繼承,后定義新的自定義屬性 } SubType.prototype = new SuperType(); // 繼承方法 Object.defineProperty( SubType.prototype, "constructor", { // 先繼承,后定義新的自定義屬性 enumerable: false, // 申明該數據屬性——constructor不可枚舉 value: SubType }); SubType.prototype.sayAge = function(){ // 先繼承,后定義新的自定義方法 alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); // "red, blue, green, black" instance1.sayName(); // "Nicholas" instance1.sayAge(); // 29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); // "red, blue, green" instance2.sayName(); // "Greg"; instance2.sayAge(); // 27
優點:
instanceOf()
和 isPrototypeOf()
也能夠用于識別基于組合繼承創建的對象。缺點:
在實現繼承的時候,無論什么情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。子類型的原型最終會包含超類型對象的全部實例屬性,但我們不得不在定義子類型構造函數時重寫這些屬性,因為子類型的原型中最好不要有引用類型值。但這在實際中,就造成了內存的浪費。
4. 原型式繼承
原型式繼承所秉承的思想是:在不必創建自定義類型的情況下,借助原型鏈,基于已有的對象創建新對象。這其中會用到 Object.create()
方法,讓我們先來看看該方法的原理代碼吧:
function object(o){ function F(){} F.prototype = o; return new F(); }
從本質上講,object()
對傳入其中的對象執行了一次淺復制。
ECMAScript 5 想通過 Object.create()
方法規范化原型式繼承。這個方法接受兩個參數:一參是被用來作為新對象原型的一個對象;二參為可選,一個為新對象定義額外屬性的對象,這個參數的格式與 Object.defineProperties()
的二參格式相同。以下為原型式繼承的示例代碼:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person, { name: { value: "Greg" } }); anotherPerson.friends.push("Rob"); alert(anotherPerson.name); //"Greg" var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
缺點:所有實例始終都會共享源對象中的引用類型屬性值。
5. 寄生式繼承
寄生式(parasitic)繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再像真的是它做了所有工作一樣返回對象。下面來看看,寄生式繼承的示例代碼:
function object(o){ function F(){} F.prototype = o; return new F(); } function createAnother(original){ var clone = object(original); // 通過調用函數創建一個新對象 clone.sayHi = function(){ // 以某種方式來增強這個對象 alert("hi"); }; return clone; // 返回這個對象 }
該繼承方式其實就是將原型式繼承放入函數內,并在其內部增強對象,再返回而已。就相當于原型式繼承寄生于函數中,故而得名寄生式繼承。
前面示范繼承模式時使用的 object() 函數不是必需的;任何能夠返回新對象的函數都適用于此模式。
缺點:不能做到函數復用,效率低下。
6. 寄生組合式繼承(推薦)
寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。其背后的基本思路是:不必為了指定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然后再將結果指定給子類型的原型。以下為寄生組合式繼承的實例代碼:
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(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); // 繼承屬性 this.age = age; } inheritPrototype(SubType, SuperType); // 繼承原型方法 SubType.prototype.sayAge = function(){ alert(this.age); };
優點:
寄生組合式繼承是最理想的繼承方式。
7. ES6 中的 extend 繼承
來看看 ES6 中 extend 如何實現繼承的示例代碼:這一塊的內容解釋,我閱讀的是這篇文章,欲知原文,請戳這里~
class Child extends Parent{ name ='qinliang'; sex = "male"; static hobby = "pingpong"; //static variable constructor(location){ super(location); } sayHello (name){ super.sayHello(name); //super調用父類方法 } }
我們再來看看 babel 編譯過后的代碼中的 _inherit() 方法:
function _inherits(subClass, superClass) { //SuperClass必須是一個函數,同時非null if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create( // 寄生組合式繼承 superClass && superClass.prototype, //原型上的方法、屬性全部被繼承過來了 { constructor: { // 并且定義了新屬性,這里是重寫了constructor屬性 value: subClass, enumerable: false, // 并實現了該屬性的不可枚舉 writable: true, configurable: true } } ); if (superClass) // 實現類中靜態變量的繼承 Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
從這里我們就可以很明顯的看出 ES6 中的 extend
語法,在內部實現繼承時,使用的是寄生組合式繼承。
下面我們來看看編譯過后,除了 _inherit()
方法外的其他編譯結果代碼:
"use strict"; var _createClass = function () { // 利用原型模式創建自定義類型 function defineProperties(target, props) { // 對屬性進行數據特性設置 for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { // 設置Constructor的原型屬性到prototype中 if (protoProps) defineProperties(Constructor.prototype, protoProps); // 設置Constructor的static類型屬性 if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _get = function get(object, property, receiver) { // 調用子類的方法之前會先調用父類的方法 // 默認從Function.prototype中獲取方法 if (object === null) object = Function.prototype; // 獲取父類原型鏈中的指定方法 var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); // 繼續往上獲取父類原型 if (parent === null) { return undefined; } else { // 繼續獲取父類原型中指定的方法 return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; // 返回獲取到的值 } else { var getter = desc.get; // 獲取原型的getter方法 if (getter === undefined) { return undefined; } return getter.call(receiver); // 接著調用getter方法,并傳入this對象 } }; function _classCallCheck(instance, Constructor) { // 保證了我們的實例對象是特定的類型 if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // 在子類的構造函數中調用父類的構造函數 function _possibleConstructorReturn(self, call) { // 一參為子類的this,二參為父類的構造函數 if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } var Child = function (_Parent) { _inherits(Child, _Parent); function Child(location) { // static variable _classCallCheck(this, Child); // 檢測this指向問題 // 調用父類的構造函數,并傳入子類調用時候的參數,生成父類的this或者子類自己的this var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, location)); _this.name = 'qinliang'; _this.sex = "male"; return _this; } _createClass(Child, [{ //更新Child類型的原型 key: "sayHello", value: function sayHello(name) { // super調用父類方法,將調用子類的super.sayHello時候傳入的參數傳到父類中 _get(Child.prototype.__proto__ || Object.getPrototypeOf(Child.prototype), "sayHello", this).call(this, name); } }]); return Child; }(Parent); Child.hobby = "pingpong";
從我的注釋中就可以看出 _possibleConstructorReturn()
函數,其實就是寄生組合式繼承中唯一一次調用超類型構造函數,從而對子類型構造函數進行實例化環境的初始化。從這點,我們可以更加確定的 ES6 中的 extend 使用的是寄生組合式繼承。
更多關于JavaScript相關內容還可查看本站專題:《javascript面向對象入門教程》、《JavaScript錯誤與調試技巧總結》、《JavaScript數據結構與算法技巧總結》、《JavaScript遍歷算法與技巧總結》及《JavaScript數學運算用法總結》
希望本文所述對大家JavaScript程序設計有所幫助。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。