在ES5中,我們經常使用方法或者對象去模擬類的使用,並基於原型實現繼承,雖然可以實現功能,但是代碼並不優雅,很多人還是傾向於用 class 來組織代碼,很多類庫、框架創造了自己的 API 來實現 class 的功能。
ES6 時代終於有了 class (類)語法,能讓我們可以用更簡明的語法實現繼承,也使代碼的可讀性變得更高,同時為以後的JavaScript語言版本添加更多的面向對象特徵打下基礎。有了ES6的class 以後媽媽再也不用擔心我們的代碼亂七八糟了,這簡直是喜大普奔的事情。ok,我們看看神奇的class.
這種寫法跟傳統的面向對象語言(比如 C++ 和 Java)差異很大。接下來我們看下ES6 類的寫法,這個就很接近於傳統面向對象語言了。如果你想了解傳統面向對象語言,這裡是一個好切入點。
二、類的傳參 constructor
在類的參數傳遞中我們用constructor( )進行傳參。傳遞參數後可以直接使用http:// 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的情況下,與新代碼同時運行。
不過,它使代碼的可讀性變得更高,並且為今後版本里更多面向對象的新特性打下了基礎。
推薦閱讀: