ES6語法——Class與繼承、修飾器

http://es6.ruanyifeng.com/阮一峯ES6

十六、Class與繼承

1、簡介

ES6的class的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。

類的數據類型是函數。constructor方法就是構造方法,直接指向“類”的本身,通過new命令生成對象實例時,自動調用該方法。this關鍵字代表實例對象。如果該方法沒有顯式定義,會被默認添加空方法constructor(){}。

定義“類”的方法時,前面不需要加function關鍵字,方法之間不需要逗號分隔,加了會報錯。類的所有方法都定義在類的prototype屬性上面。類的內部所有定義的方法,都是不可枚舉的,這一點與ES5的行爲不一致。

實例的屬性除非顯式定義在其本身(this對象),否則都是定義在原型上(class)。

類必須使用new調用new Point(2, 3);,否則會報錯Point(2,3);,這是它跟普通構造函數的一個主要區別。

function Point(x,y){ // ES5
  this.x=x;
  this.y=y;
}
Point.prototype.toString=function(){
  return '('+this.x+","+this.y+')';
}

class Point{ // ES6
  constructor(x,y){ // 構造方法
    this.x=x;
    this.y=y;
  }
  toString(){ // 不需加function關鍵字,不需逗號分隔
    return '('+this.x+","+this.y+')';
  }
}

console.log(typeof Point); // "function"
console.log(Point.prototype.constructor===Point); // true

let p=new Point(1,2);
console.log(p); // {x: 1, y: 2}
console.log(p.constructor===Point.prototype.constructor); // true

// 向一個類添加多個新方法
Object.assign(Point.prototype,{
  toString(){},
  toValue(){}
});

實例屬性的新寫法:實例屬性除了在constructor()方法裏定義,也可以直接寫在類的最頂層。這時,不需要在實例屬性前面加上this。

與ES5一樣,在“類”的內部可以使用get和set關鍵字,對某個屬性設置取值函數(getter)和存值函數(setter),攔截該屬性的存取行爲。存值函數和取值函數是設置在屬性的Descriptor對象上的。

class MyClass{
  constructor(){
  }
  get prop(){
    return "getter";
  }
  set prop(value){
    console.log('setter: '+value);
  }
}
let inst=new MyClass();
inst.prop=123; // setter: 123
console.log(inst.prop); // getter

類的屬性名,可以採用表達式。

let methodName='getArea';

class Square{
  constructor(length) {
    // ...
  }
  [methodName](){
    // ...
  }
}

與函數一樣,類也可以使用表達式的形式定義。這個類的名字是MyClass而不是Me,Me只在Class的內部代碼可用,指代當前類。 如果類的內部沒用到,可以省略Me。採用Class表達式,可以寫出立即執行的Class。

const MyClass=class Me{
  getClassName(){
    return Me.name;
  }
};

const MyClass=class{ /* ... */ }; // 省略Me

注意點:

類和模塊的內部,默認就是嚴格模式,ES6實際上把整個語言升級到了嚴格模式。

類不存在變量提升(hoist),這一點與 ES5 完全不同。與繼承有關,必須保證子類在父類之後定義。

ES6的類只是ES5的構造函數的一層包裝,所以函數的許多特性都被Class繼承,包括name屬性。

如果某個方法之前加上星號(*),就表示該方法是一個Generator函數。Symbol.iterator方法返回一個Foo類的默認遍歷器,for...of循環會自動調用這個遍歷器。

單獨使用類中的方法時,注意this的指向。

2、靜態方法和靜態屬性

在一個方法前加static關鍵字,表示該方法不會被實例繼承,而是直接通過類來調用,稱爲“靜態方法”。靜態方法的this關鍵字指的是類,而不是實例。靜態方法可以與非靜態方法重名。父類的靜態方法,可以被子類繼承,可以從super對象上調用。

class Foo{
  static classMethod(){
    return "hello"
  }
}

Foo.classMethod() // "hello"
var foo=new Foo();
foo.classMethod() // Uncaught TypeError: foo.classMethod is not a function

靜態屬性指的是Class本身的屬性,而不是定義在實例對象上的屬性。靜態屬性定義在類的外部,整個類生成以後,再生成靜態屬性,Foo.prop=1;。另一種寫法是在實例屬性法的前面,加上static關鍵字,目前只是提案。

class Foo{ // 老寫法
  // ...
}
Foo.prop=1;

class Foo{ // 新寫法
  static prop=1;
}

3、私有方法和私有屬性

私有方法和私有屬性,是只能在類的內部訪問的方法和屬性,外部不能訪問。這是常見需求,有利於代碼的封裝,但ES6不提供,只能通過變通方法模擬實現。

(1)在命名上加以區別,私有方法名之前加_,但在類的外部仍可以調用該方法。

(2)將私有方法移出模塊,因爲模塊內部的所有方法都是對外可見的。

class Widget{
  foo(baz){
    bar.call(this,baz);
  }
}
function bar(baz){
  return this.snaf=baz;
}

(3)利用Symbol值的唯一性,將私有方法的名字命名爲一個Symbol值,導致第三方無法獲取到它們 。

const bar=Symbol('bar');
const snaf=Symbol('snaf');

export default class myClass{
  foo(baz){ // 公有方法
    this[bar](baz);
  }
  [bar](baz){ // 私有方法
    return this[snaf]=baz;
  }
};

(4)提案,在屬性名或方法名之前,使用#表示。

 私有屬性也可以設置getter和setter方法。

4、new.target屬性

new.target屬性一般用在構造函數之中,返回new命令作用於的那個構造函數。如果構造函數不是通過new命令調用的,返回undefined,因此這個屬性可以用來確定構造函數是怎麼調用的。

Class內部調用new.target,返回當前Class。子類繼承父類時,會返回子類。

5、繼承

通過extends關鍵字實現繼承。

super關鍵字表示父類的構造函數,用來新建父類的this對象。子類必須在constructor方法中調用super方法,否則新建實例時會報錯。只有調用super之後,纔可以使用this關鍵字,否則會報錯。因爲子類this對象,必須先通過父類的構造函數得到與父類同樣的實例屬性和方法,然後再加上子類自己的實例屬性和方法。如果不調用super方法,子類就得不到this對象。

class ColorPoint extends Point{
  constructor(x,y,color){
    super(x,y); // 調用父類的constructor(x, y)
    this.color=color;
  }
  toString(){
    return this.color+' '+super.toString(); // 調用父類的toString()
  }
}

十七、Decorator

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章