es5繼承和es6繼承中靜態方法、靜態屬性的差異

Arthur1472612286547發表於2019-04-17

最近在看vue的原始碼,其中有一部分牽扯到了繼承,看到es5中靜態方法的"繼承"是這樣寫的

      const Sub = function VueComponent (options) {
        this._init(options)
      }
      Sub.prototype = Object.create(Super.prototype)
      Sub.prototype.constructor = Sub
        ...
      Sub.extend = Super.extend
      Sub.mixin = Super.mixin
      Sub.use = Super.use
複製程式碼

es5的繼承大家都知道(通過原型鏈實現原型屬性的繼承,通過建構函式實現例項屬性的繼承),但程式碼中靜態方法是直接從父類賦給子類的,這就想到的一個問題?靜態方法沒有繼承嗎?帶著這個問題研究了一下靜態方法在es5繼承中和es6繼承中的差異

es5中靜態方法的繼承

    // 定義父類
    function Super(name, age) {
      this.name = name;
      this.age = age;
    };
    Super.prototype.sayName = function() {
      return this.name;
    };
    // 定義屬性
    Super.num = 1;
    // 定義靜態方法
    Super.sayWord = function(word) {
      return word;
    };
    // 定義子類
    function Sub(name, age, sex) {
      // 繼承例項屬性
      Super.call(this, name, age);
      this.sex = sex;
    }
    // 繼承父類的原型方法+原型屬性
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    
    var instance = new Sub('張三', '18', '男');
複製程式碼

分別列印出來看下結果:

    console.log(Super.sayWord('hello world'));
    // print TypeError: Sub.sayWord is not a function
    
    console.log(Sub.sayWord('hello world'));
   // print TypeError: Sub.sayWord is not a function
    
    console.log(instance.sayWord('hello world'));  //  例項是無法獲取到靜態方法的
    // print instance.sayWord is not a function
複製程式碼

靜態屬性和靜態方法類似,大家可以自行驗證,通過上面的我們可以得出下面的結論: es5的類繼承:子類是無法繼承父類的靜態屬性和靜態方法的,只是通過傳遞

其中有個需要注意的地方:靜態方法沒辦法獲取到this,由於JavaScript中的類也是一個物件,靜態方法相當於是這個類物件的一個屬性方法,但是this指的是當前例項物件而不是類物件,所以是無法取到的,下面看下es6中的

es6中靜態方法的繼承

class Super {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayName() {
    return this.name;
  }

  static sayWord(word) {
    return word;
  }

  static get num() {
    return this._num; // 因為類也是一個物件 所以_num 只是這個類上的屬性 並非例項屬性
  }

  static set num(n) {
    this._num = n;
  }
}

class Sub extends Super {
  constructor(name, age, sex) {
    super(name, age);
    this.sex = sex;
  }
}

const instance = new Sub('張三', '18', '男');
複製程式碼

下面我們分別看下靜態方法和靜態屬性

console.log(Super.sayWord('hello world'));
// print hello world
console.log(Sub.sayWord('hello world'));
// print hello world
console.log(instance.sayWord('hello world'));
// print TypeError: instance.sayWord is not a function
複製程式碼

可以看出es6繼承中,靜態方法被繼承下來,這點與es5還是有差異的,再看靜態屬性, es6中的靜態屬性可以直接[類名].[屬性名]這樣的定義,也可以如上面那樣定義(get,set),只是get,set這樣的定義我們可以更好進行的控制

Super.num = 3;
console.log(Super.num);
// print 3
console.log(Sub.num);
// print 3
複製程式碼

看的出,靜態屬性也被繼承了,但是這樣也有問題,當靜態屬性是引用型別時,子類和父類指向的同一個地址,父類如果發生變化子類也會跟著變化,這是我們不希望看到的

Super.num = [];

Sub.num.push(1);
console.log(Super.num);
// print [1]
console.log(Sub.num);
// print [1]

Super.num.push(2);
console.log(Super.num);
// print [1,2]
console.log(Sub.num);
// print [1,2]
複製程式碼

我們修改下靜態屬性num的get方法,使其只獲取自己當前類的值,而非繼承的值

  static get num() {
    return this.hasOwnProperty('_num') ? this._num : undefined;
  }
複製程式碼

再測試一下:

Super.num = [];
// Sub.num.push(1);
// print TypeError: Cannot read property 'push' of undefined 因為Sub.num得到的是 undefined
Sub.num=1;
console.log(Super.num);
// print []
console.log(Sub.num);
// print 1
Super.num.push(2);
console.log(Super.num);
// print [2]
console.log(Sub.num);
// print 1
複製程式碼

可以得出:es6中的靜態方法和靜態屬性都可以被繼承下來的,只是靜態屬性的繼承需要稍作處理,否則就被共享了

下面我們再總結下:

  1. es5和es6實現繼承的方式是不同的,前者通過原型鏈實現對父類原型方法、原型屬性的繼承,通過建構函式的呼叫實現對例項方法,例項屬性的呼叫,後者通過extends關鍵字實現繼承
  2. es5中靜態方法、靜態屬性是無法通過繼承下來的,只能通過賦值傳遞,如最開始的demo,但es6中則是可以的

相關文章