最近在看es2015的一些語法,最實用的應該就是繼承這個新特性了。比如下面的程式碼:
1 $(function(){ 2 class Father{ 3 constructor(name, age){ 4 this.name = name; 5 this.age = age; 6 } 7 8 show(){ 9 console.log(`我叫:${this.name}, 今年${this.age}歲`); 10 } 11 }; 12 class Son extends Father{}; 13 14 let son = new Son('金角大王', 200); 15 son.show();//return 我叫:金角大王, 今年200歲 16 17 });
這是一個最簡單的繼承。在Son類中並沒有任何的自己的屬性和方法,來看一下f12中的結構
也是不例外的使用了原型鏈來實現的繼承,那麼在es5中如果要實現這個繼承應該怎麼做?
使用babel把這段程式碼翻譯成es5的語法,發現程式碼如下:
1 "use strict"; 2 3 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) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 5 function _possibleConstructorReturn(self, call) { 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; } 6 7 function _inherits(subClass, superClass) { 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: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 8 9 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 10 11 /** 12 * Created by liuyc14 on 2016/6/28. 13 */ 14 15 var Father = function () { 16 function Father(name, age) { 17 _classCallCheck(this, Father); 18 19 this.name = name; 20 this.age = age; 21 } 22 23 _createClass(Father, [{ 24 key: "show", 25 value: function show() { 26 console.log("我叫:" + this.name + ", 今年" + this.age + "歲"); 27 } 28 }]); 29 30 return Father; 31 }(); 32 33 ; 34 35 var Son = function (_Father) { 36 _inherits(Son, _Father); 37 38 function Son() { 39 _classCallCheck(this, Son); 40 41 return _possibleConstructorReturn(this, Object.getPrototypeOf(Son).apply(this, arguments)); 42 } 43 44 return Son; 45 }(Father); 46 47 ;
這些是babel編譯完成後生成的es5語法的實現程式碼,看起來多了很多東西。
不著急,挑出幾個重點來看一下(以後的例子都使用es5語法)
1. _createClass 方法,建立一個類,用到了defineProperties方法,就是給第一個引數的target物件,附加所有第二個引數的屬性
2. _inherits 方法,實現繼承的核心,用到了Object.create 和 Object.setPrototypeOf 方法
Object.create 方法:
這個方法接受兩個引數,第一個引數為要繼承的物件,第二引數為附加屬性,返回一個建立後的物件。
舉個例子:
1 function Father(name, age){ 2 this.name = name; 3 this.age = age; 4 } 5 6 Father.prototype.show = function () { 7 console.log('我叫:' +this.name+', 今年'+this.age+'歲'); 8 }; 9 10 var obj = Object.create(Father.prototype); 11 console.log(obj.name); //return undefined 12 obj.show(); // return 我叫:undefined, 今年undefined歲
上面這個例子中,使用create方法,建立了一個obj物件,而且這個物件繼承了Father.prototype物件的屬性。(只有一個show方法,並沒有 name 和 age 屬性)
看到了這個作用以後,我們就可以使用create方法來實現es5的繼承了
1 function Father(name, age){ 2 this.name = name; 3 this.age = age; 4 } 5 6 Father.prototype.show = function () { 7 console.log('我叫:' +this.name+', 今年'+this.age+'歲'); 8 }; 9 10 function Son(name, age){ 11 Father.call(this, name, age); 12 } 13 Son.prototype = Object.create(Father.prototype); 14 Son.prototype.constructor = Son; 15 Son.prototype.show = function () { 16 console.log('我是子類,我叫' + this.name + ', 今年' + this.age + '歲了'); 17 }; 18 var s = new Son('銀角大王', 150); //return 我是子類,我叫銀角大王, 今年150歲了 19 s.show();
上面的Son類在定義時,使用Father.call來繼承Father的例項屬性,使用Object.create方法來繼承Father的原型,這樣就完整的實現了繼承,來看一下分析圖
Son的例項s,在原型中有自己的show方法,再往上查詢Father的原型,還可以看到show的原型,很清晰的層次結構
其實我們也可以不使用Object.create方法,使用Object.setPrototypeOf 方法來代替,達到同樣的效果
把之前例子裡第13行程式碼由
Son.prototype = Object.create(Father.prototype); =>
Object.setPrototypeOf(Son.prototype, Father.prototype);
這兩行程式碼的效果是一樣的,第二種方法更直觀一些,就是把Son.prototype.__proto__ = Father.prototype 這樣。
最後一個問題,我們如何才能向C#或者java裡那樣,在子型別中呼叫父類的方法呢?比如Son.prototype.show=function(){super.show()}這樣
可以使用Object.getPrototypeOf(Son.prototype)方法來獲取原型鏈的上一級,這樣就可以獲取到Father.prototype物件了,然後呼叫show()方法
1 Son.prototype.show = function () { 2 Object.getPrototypeOf(Son.prototype).show(); 3 console.log('我是子類,我叫' + this.name + ', 今年' + this.age + '歲了'); 4 };
但是呼叫Son的show方法,會log出: 我叫:undefined, 今年undefined歲; 我是子類,我叫銀角大王, 今年150歲了
為什麼會有undefined?看看剛才我們的f12結構圖,Father.prototype中是沒有name 和 age 屬性的,那麼怎麼辦?使用call方法啊!
下面貼出完整的類繼承實現程式碼:
1 function Father(name, age){ 2 this.name = name; 3 this.age = age; 4 } 5 6 Father.prototype.show = function () { 7 console.log('我叫:' +this.name+', 今年'+this.age+'歲'); 8 }; 9 10 function Son(name, age){ 11 Father.call(this, name, age); 12 } 13 Object.setPrototypeOf(Son.prototype, Father.prototype); 14 Son.prototype.constructor = Son; 15 Son.prototype.$super = Object.getPrototypeOf(Son.prototype);//使用$super屬性來指向父類的原型 16 Son.prototype.show = function () { 17 this.$super.show.call(this); 18 console.log('我是子類,我叫' + this.name + ', 今年' + this.age + '歲了'); 19 }; 20 var s = new Son('銀角大王', 150); 21 s.show();
OK,今天的總結寫完了,跟流水賬一樣,大家湊活看吧