es6繼承 vs js原生繼承(es5)

橙子瓣發表於2016-06-28

最近在看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,今天的總結寫完了,跟流水賬一樣,大家湊活看吧

 

相關文章