1.Class類的介紹 在ES6中新增了Class類的概念,讓語法看起來更像是面向對象編程,其實這可以說是一個語法糖,ES5可以做到Class絕大部分功能,但也有一些不同。在ES6以前,可以通過構造函數來模擬類的概念,如下所示 在引入了Class關鍵字後,可以這樣做,如下所示 如上所示,便是ES6中 ...
1.Class類的介紹
在ES6中新增了Class類的概念,讓語法看起來更像是面向對象編程,其實這可以說是一個語法糖,ES5可以做到Class絕大部分功能,但也有一些不同。在ES6以前,可以通過構造函數來模擬類的概念,如下所示
function Student(name,age){ this.name = name; this.age = age; } Student.prototype = { constructor:Student, show:function(){ console.log(this.name,this.age); } }; var xiaoming = new Student("xiaoming",20); xiaoming.show(); xiaoming 20
在引入了Class關鍵字後,可以這樣做,如下所示
class Person{ constructor(name,age){ //構造函數,預設返回this對象,如果強制返回其他對象也是被允許的 this.name = name; this.age = age; } show(){ console.log(this.name,this.age); } } var s = new Person('test',30); s.show(); test 30
如上所示,便是ES6中類的定義,上面的constructor函數是類的構造函數,如果不寫,則會自動創建一個constructor空構造函數,並且方法之間不允許有逗號,否則會報錯,如下所示
class Person{ show(){ alert("show"); }, hide(){ alert("hide"); } } var s = new Person(); s.show(); SyntaxError: invalid property id
其實類的數據類型就是函數,類本身就指向構造函數,如下所示
class Person{ constructor(name,age){ this.name = name; this.age = age; } show(){ console.log(this.name,this.age); } } typeof Person "function" Person.prototype.constructor===Person true var p = new Person('test',20); undefined p.constructor function Person() p.constructor == Person true
prototype屬性在類上依舊存在,且類的方法都定義在類的prototype上面,如下所示
var person = new Person('person',100); person.constructor === Person.prototype.constructor true
因此可以在類的prototype上面定義的新方法為類擴展方法,如下所示
Object.assign(Person.prototype,{show(){console.log(this.name);}}); person.show(); var a = new Person('nihao',20); a.show(); person nihao
但是類內部定義的方法是不可枚舉的,這和ES5的構造函數還是有很大的區別,如下所示
Object.getOwnPropertyNames(Person.prototype) Array [ "constructor", "show" ] Object.keys(Person.prototype) Array [ ]
類的屬性名可以使用表達式來定義,如下所示
var show = 'show'; class Test{ constructor(){ console.log("class test constructing..."); } [show](){ console.log('show'); } } var test = new Test(); test[show](); class test constructing... show
在生成類的實例對象時,必須使用new關鍵字,如果直接像調用函數那樣調用類,則會報錯,如下所示
var t1 = Test(); TypeError: class constructors must be invoked with |new|
類的所有實例共用一個原型,實例對象可以通過__proto__屬性來訪問原型,因此可以通過該屬性為原型添加方法,如下所示
test.__proto__.say = () => "hello prototype!"; test.say(); new Test().say(); class test constructing... "hello prototype!" test.say()
類Class也可以像Function一樣,採用表達式形式來定義,如下所示
var Aclass = class Bclass{ constructor(){ console.log(Bclass.name);//只有在類的內部才能訪問到Bclass } show(){ console.log('Aclass...'); } }; var a = new Aclass(); a.show(); Bclass Aclass...
因此我們可以寫出類似立執行函數的立執行類,如下所示
var iifeInstance = new class{ constructor(text){ this.text = text; } show(){ console.log(this.text); } }('iife testing...'); iifeInstance.show(); iife testing...
類的定義不存在變數聲明提升,這和ES5函數不同,如下圖所示
var a = new Dclass(); class Dclass{ } ReferenceError: can't access lexical declaration `Dclass' before initialization
2.Class類的繼承
Class類的繼承通過關鍵字extends來實現,和ES5通過修改原型鏈實現繼承不同,如下所示
class Person{ constructor(name,age){ this.name = name; this.age = age; } print(){ console.log(this.name,this.age); } } class Student extends Person{ constructor(name,age,sno){ super(name,age); //調用父類的構造函數,必須存在且需放在子類使用this對象之前,否則將報錯 this.sno = sno; } print(){ super.print(); console.log(this.sno); } } var s = new Student('xiaoming',20,"21014130217"); s.print(); xiaoming 20 21014130217
值得註意的是在定義子類構造函數時,一定要調用父類的構造函數且必須在子類使用this對象之前,因為子類不存在this對象,需要使用super得到父類的this對象,然後對其進行改寫加工。如果預設構造函數,則會自動創建一個預設的構造函數並調用父類的構造函數
子類的原型prototype是父類的實例,有點像原型鏈繼承,子類__proto__指向父類,這和函數的預設繼承不同。如下所示
Student.prototype instanceof Person true Student.prototype.__proto__ === Person.prototype true s.__proto__ === Student.prototype true Student.__proto__ === Person true
可以使用Object.getPrototypeOf獲取子類的父類,如下所示
Object.getPrototypeOf(Student) === Person true
3.原生構造函數的繼承
在ES5中不能繼承原生的構造函數,例如Array,Date,Object,Function等等,但是在ES6中可以實現對原生構造函數的繼承,如下所示
class MyArray extends Array{ constructor(){ super(); this.sign = "myArray"; } } var myArr = new MyArray(); console.log(myArr); myArr.push(1,2,3); console.log(myArr); Array [ ] Array [ 1, 2, 3 ] console.log(myArr.sign); "myArray"
但是在繼承Object時,有一點差異就是調用super給Object構造函數傳參時會被忽略,這是ES6在實現時檢測到不是以new Object形式調用構造函數,則忽略參數,如下所示
class MyObj extends Object{ constructor(arg){ super(arg); this.sign = "MyObj"; } } var myObj = new MyObj({x:1}); console.log(myObj.x); undefined
3.類的getter和setter函數,與ES5中的一樣,都是對類屬性存取行為的攔截,如下所示
class Xclass{ constructor(){ console.log('Xclass initializing...'); } set x(val){ console.log(val); } get x(){ return 2; } } var xIns = new Xclass(); xIns.x = 4; console.log(xIns.x); Xclass initializing... 4 2
4.類的靜態方法與靜態屬性
靜態方法與屬性不能被實例對象調用,靜態方法可以在類內部定義,但靜態屬性則不能,靜態方法可以被子類繼承,如下所示
class StaticTest{ static test(){ console.log('static...'); } } class SubStatic extends StaticTest{ show(){ console.log('substatic show...'); } } StaticTest.test(); SubStatic.test(); new SubStatic().show(); new StaticTest().test(); static... 2次
substatic show...
TypeError: (intermediate value).test is not a function
靜態屬性的定義則是直接使用ClassName.prop形式來定義,如下所示
class StaticTest{ static test(){ console.log('static...'); } } StaticTest.xxx = "hello class static variable"; StaticTest.xxx; "hello class static variable"
5.new.target
在ES6中為new命令添加了一個target屬性,該屬性在構造函數中返回new命令作用於的那個函數,如果不是以new命令調用構造函數則返回undefined,如下所示
class Target{ constructor(){ console.log(Object.is(new.target,Target)); } } var target = new Target(); true
有了這個屬性,我們可以強制構造函數必須以new命令實例化,如下所示
function Animal(name){ if(!new.target) throw new Error('必須使用new實例化Animal'); this.name = name; } var duck1 = new Animal('duck1'); console.log(duck1.name); var duck2 = Animal('duck2'); duck1 Error: 必須使用new實例化Animal
也可以用來模仿抽象類,不能被實例化,只能被繼承使用
class Car{ constructor(brand){ if(new.target === Car) throw new Error('Car類不能被實例化'); this.brand = brand; } } class Baoma extends Car{ constructor(brand){ super(brand); } showBrand(){ console.log(this.brand); } } var baoma = new Baoma('baoma'); baoma.showBrand(); baoma var car = new Car('car'); Error: Car類不能被實例化
在實例化子類時,父類的new.target指向的是子類。