ES6專題— class與面向對象編程

在ES5中,我們經常使用方法或者對象去模擬類的使用,並基於原型實現繼承,雖然可以實現功能,但是代碼並不優雅,很多人還是傾向於用 class 來組織代碼,很多類庫、框架創造了自己的 API 來實現 class 的功能。

ES6 時代終於有了 class (類)語法,能讓我們可以用更簡明的語法實現繼承,也使代碼的可讀性變得更高,同時爲以後的JavaScript語言版本添加更多的面向對象特徵打下基礎。有了ES6的class 以後媽媽再也不用擔心我們的代碼亂七八糟了,這簡直是喜大普奔的事情。ok,我們看看神奇的class.

一、 類的定義

1.1 ES5 模擬類定義

function Person( name , age ) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    return '我叫' + this.name + ',今年' + this.age + '歲';
}
var  p = new Person('大彬哥',18);  // Person {name: "大彬哥", age: 18}
p.say()                           //"我叫大彬哥,今年18歲"

使用ES5語法定義了一個Person類,該類有name和age兩個屬性和一個原型say方法。

這種寫法跟傳統的面嚮對象語言(比如 C++ 和 Java)差異很大。接下來我們看下ES6 類的寫法,這個就很接近於傳統面嚮對象語言了。如果你想了解傳統面嚮對象語言,這裏是一個好切入點。

1.2 ES6 class類定義

class Person {
  constructor( name , age ) {
    this.name = name;
    this.age = age;
  }
  say() {
    return '我叫' + this.name + ',今年' + this.age + '歲';
  }
}
var  p = new Person('大彬哥',18);  // Person {name: "大彬哥", age: 18}
p.say()                           //"我叫大彬哥,今年18歲"

上面代碼定義了一個同樣的Person類,constructor方法就是構造方法,而this關鍵字則代表實例對象,這更接近傳統語言的寫法。

注意:

雖然引入了class關鍵字,但ES6中並沒有真的引入類這個概念,通過class定義的仍然是函數:

console.log(typeof Person); // 'function'

所以說,class僅僅是通過更簡單直觀的語法去實現原型鏈繼承。這種對語言功能沒有影響、但是給程序員帶來方便的新語法,被稱爲語法糖。

圖片描述
二、類的傳參 constructor

在類的參數傳遞中我們用constructor( )進行傳參。傳遞參數後可以直接使用this.xxx進行調用。

class Person {
   constructor(a,b){
        this.a=a;
        this.b=b;
   }
   add(){
        return this.a + this.b;
   }
}
let p = new Person(18,30);
console.log(p.add());  // 48 (18+30)

我們用constructor來傳遞參數,然後用了一個add方法,把參數相加。這和以前我們的函數傳參方法有些不一樣,所以小夥伴們要注意轉換下思維。

三、靜態方法

在面嚮對象語言中,靜態方法是指不需要實例化,可以通過類名直接調用的方法,但靜態方法不會繼承到類實例中,因此靜態方法經常用來作爲工具函數。比如我們經常用的Math.random(),我們並不需要先new 一個Math然後再去用,一是如果作者那麼設計JS一來是沒必要,二是用起來太繁瑣。

在使用函數模擬類時,可以像下面這樣定義靜態方法:

function Person(name, sex) {}

Person.walk = function() {
    console.log('我會走路')
}

Person.walk();  // 我會走路
var person = new Person();
person.walk();  // TypeError

在ES6 class類定義中,可以使用static關鍵字定義:

class Person {
  constructor() {}

  static walk(){
      console.log('我會走路')
  }
}
Person.walk();  // 我會走路
var person = new Person();
person.walk();  // TypeError

static關鍵字是ES6的另一個語法糖,static 使靜態方法聲明也成爲了一個一等公民。

於此同時,靜態方法也是可以從子類中的super對象上調用的。

class Person {
  constructor() {}

  static walk(){
      return '我會走路'
  }
}

class People extends Person {
  static walk() {
    return super.walk() + ', 我還會跑步';
  }
}

People.walk();  //"我會走路, 我還會跑步"

四、封裝與繼承

封裝和繼承,是面向對象編程三大核心特徵中非常重要的兩個,封裝和繼承在我們實際生活中也有非常多的應用。舉個例子,你去驢肉火燒店去吃飯。

老闆把驢肉面和火燒一起買,起名字叫“精英驢火套餐”,這就是封裝。

而進去以後跟老闆說,老闆給我來個“82年的驢火套餐”這就是繼承。當然了你不僅僅能繼承,還能擴展自己的功能。比如你可以跟老闆說,老闆再給我加一個驢板腸。說的我都餓了,不過我們還是教編程的專欄,不是開店的專欄,我們繼續,看看ES6裏面怎麼玩繼承。

4.1 extends

舊的原型繼承有時看起來讓人非常頭疼。

function Child(firstName, lastName, age) {
  Parent.call(this, firstName, lastName)
  this.age = age
}

Child.prototype = Object.create(Parent.prototype)
Child.constructor = Child

ES6中新的extends關鍵字解決了這個問題:

class Child extends Parent {}

上面代碼定義了一個Child類,該類通過extends關鍵字,繼承了Parent類的所有屬性和方法。

由於沒有在Child內部寫任何代碼,所以這兩個類完全一樣,等於複製了一個Parent類。

之後,我們在Child內部加上代碼:

class Child extends Parent {
  constructor(firstName, lastName, age) {
    super(firstName, lastName)  
    // 調用父類的constructor(firstName, lastName)
    this.age = age
  }
  speak(){
    return this.age + ' ' + super.speak(); 
    // 調用父類的speak()
  }
}

使用簡介的 extends 達到繼承的目的,而非雜亂的 Object.create()、.proto、Object.setPrototypeOf(),這樣能讓我們更順利的擴充功能。
圖片描述

4.2 super

super這個關鍵字,既可以當作函數使用,也可以當作對象使用。在這兩種情況下,它的用法完全不同。

(1)super作爲函數調用

代表父類的構造函數,ES6中規定,子類的構造函數必須執行一次super函數。

class A {}

class B extends A {
  constructor() {
    super();
  }
}

上面代碼中,子類B的構造函數之中的super(),代表調用父類的構造函數,這是必須的,否則 JavaScript 引擎會報錯。

注意,super雖然代表了父類A的構造函數,但是返回的是子類B的實例,即super內部的this指的是B,因此super()在這裏相當於A.prototype.constructor.call(this)。

(2)super作爲對象時,指向父類的原型對象。

class A {
  p() {
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

與Java一樣,JavaScript也使用extends關鍵字實現繼承,子類中可以通過super關鍵字調用父類:

在 constructor 裏面,super 的用法是 super()。它相當於一個函數,調用它等於調用父類的 constructor 。

但在普通方法裏面,super 的用法是 super.prop 或者 super.method(),它相當於一個指向對象的 [[Prototype]] 的屬性。

4.3 getter(取值函數)、 setter(存值函數)

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

class Person {
  constructor() {}
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let p = new Person();
p.prop = 666;   // setter: 666
p.prop           // 'getter'

五、總結

圖片描述

無論學什麼知識,最重要也是最基礎的,要實現思想上的轉變,目前大部分框架和庫,都採用了面向對象方式編程。而且在工作中,要書寫中型和大型的項目也經常使用面向對象方式編程,可能大家習慣了面向過程方式編程,其實面向對象方式編程一旦習慣了,會讓我開發和思路更寬闊和易於開發項目。

從學習javascript基礎開始的時候,我們就瞭解了js中的保留字,js中並沒有用到,但是將來可能會用到的未來關鍵字。這些保留字中就包括:class、extends、super。這些就是爲將來在js中支持面向對象的類機制而預留的。

果不其然,現在ES6語法中使用到了這些保留字,這些保留字成功升級成了關鍵字,可見當時javascript的設計者還是很有前瞻眼光的。

通過這些新的關鍵字,使類成爲了JS中一個新的一等公民。但是目前爲止,這些關於類的新關鍵字僅僅是建立在舊的原型系統上的語法糖。這樣做的原因是爲了保證向後兼容性。也就是,舊代碼可以在不做任何hack的情況下,與新代碼同時運行。

不過,它使代碼的可讀性變得更高,並且爲今後版本里更多面向對象的新特性打下了基礎。

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