Class
類徹底能夠看做構造函數的另外一種寫法,類的數據類型其實就是函數,類自己也就是其實例的構造函數。使用的時候也是使用 new
命令。javascript
class Person { constructor(name,age) { this.name = name; this.age = age; } getName(){ console.log(`My name is ${this.name}!`) } static sayHi() { console.log('Hi'); } } let p = new Person(); typeof Person // "function" p instanceof Person // true
constructor
方法就是類的構造方法,this
關鍵字表明實例對象。其對應的也就是 ES5 的構造函數 Person
。java
constructor
方法是類的默認方法,經過 new
命令生成對象實例時,會自動調用該方法。一個類必須有 constructor
方法,若是沒有顯式定義,會默認添加一個空的 constructor
方法。函數
class Person {} Person.prototype // {construtor:f}
constructor
方法默認返回實例對象,也徹底能夠 return
另一個對象。this
class Foo { constructor(){ return Object.create(null); } } new Foo() instanceof Foo // false
類必須使用 new
調用,也就是 constructor
方法只能經過 new
命令執行,不然會報錯。prototype
class Foo { constructor(){} } Foo() // TypeError: Class constructor Foo cannot be invoked without 'new'
Class
類依舊存在 prototype
屬性,且類的全部方法都定義在 prototype
屬性上面。code
class Person { constructor() {} aaa(){} bbb(){} } Object.getOwnPropertyNames(Person.prototype) // ["constructor", "aaa", "bbb"]
prototype
對象的 constructor
屬性,也是直接指向類自己。對象
Person.prototype.constructor === Person // true p.constructor === Person // true
類的新方法可經過 Object.assign
向 prototype
一次性添加多個。繼承
class Person { constructor() {} } Object.assign(Person.prototype, { aaa(){}, bbb(){} })
注意,定義類的時候,前面不須要加上 function
關鍵字,也不須要逗號分隔。ip
與 ES5 構造函數的不一樣的是,Class
內部全部定義的方法,都是不可枚舉的。ci
class Person { constructor() {} aaa(){} } Object.keys(Person.prototype) // [] Object.getOwnPropertyNames(Person.prototype) // ["constructor", "aaa"]
而 ES5 的構造函數的 prototype
原型定義的方法是可枚舉的。
let Person = function(){}; Person.prototype.aaa = function(){}; Object.keys(Person.prototype) // ["aaa"] Object.getOwnPropertyNames(Person.prototype) // ["constructor", "aaa"]
Class 類的屬性名,能夠採用表達式。
let methodName = 'getName'; class Person { constructor() {} [methodName](){} } Object.getOwnPropertyNames(Person.prototype) // ["constructor", "getName"]
與 ES5 同樣,在 Class
內部可使用 get
和 set
關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲。
class Person { constructor() { this.name = 'dora'; } get author() { return this.name; } set author(value) { this.name = this.name + value; console.log(this.name); } } let p = new Person(); p.author // dora p.author = 666; // dora666
且其中 author
屬性定義在 Person.prototype
上,但 get
和 set
函數是設置在 author
屬性描述對象 Descriptor
上的。
Object.getOwnPropertyNames(Person.prototype) // ["constructor", "author"] Object.getOwnPropertyDescriptor(Person.prototype,'author') // { get: ƒ author()(), // set: ƒ author()(value), // ... // }
類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。但若是在一個方法前,加上 static
關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。
class Person { static sayHi() { console.log('Hi'); } } Person.sayHi() // "Hi" let p = new Person(); p.sayHi() // TypeError: p.sayHi is not a function
若是靜態方法包含 this
關鍵字,這個 this
指的是類,而不是實例。靜態方法能夠與非靜態方法重名。
class Person { static sayHi() { this.hi(); } static hi(){ console.log('hello') } hi(){ console.log('world') } } Person.sayHi() // "hello"
實例屬性除了定義在 constructor()
方法裏面的 this
上面,也能夠定義在類的最頂層。此時定義的時候,屬性前面不須要加上 this
。而在類內部其它地方調用的時候,須要加上 this
。
class Person { name = 'dora'; getName() { return this.name; } } let p = new Person(); p.name // "dora" Object.keys(p) // ["name"]
這種寫法的好處是,全部實例對象自身的屬性都定義在類的頭部,看上去比較整齊,寫法簡潔,一眼就能看出這個類有哪些實例屬性。
Class
子類能夠經過 extends
關鍵字實現繼承。
class Person { constructor() {} sayHi() { return 'Hi'; } } class Teacher extends Person { constructor() { super(); } } let t = new Teacher(); t.sayHi(); // "Hi"
子類必須在 constructor
方法中調用 super()
方法,不然新建實例時會報錯。
若是子類沒有定義 constructor
方法,這個方法會被默認添加,且子類默認添加的 constructor
方法都會默認執行 super()
方法。
class Teacher extends Person { } let t = new Teacher(); t.sayHi(); // "Hi"
等同於
class Teacher extends Person { constructor(...args) { super(...args); } }
super
這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。用法徹底不一樣。
super
做爲函數調用時,表明父類的構造函數。子類的構造函數必須執行一次 super()
方法。
由於 ES6 的繼承機制與 ES5 構造函數不一樣,ES6 的子類實例對象 this
必須先經過父類的構造函數建立,獲得與父類一樣的實例屬性和方法後再添加子類本身的實例屬性和方法。所以若是不調用 super()
方法,子類就得不到 this
對象。
super
雖然表明了父類的構造函數,但返回的是子類的實例,即經過super
執行父類構造函數時,this
指的都是子類的實例。也就是 super()
至關於 Person.call(this)
。
class A { constructor() { console.log(this.constructor.name) } } class B extends A { constructor() { super(); } } new A() // A new B() // B
做爲函數時,super()
只能在子類的構造函數之中,用在其餘地方就會報錯。
在普通方法中指向父類的 prototype
原型
super
做爲對象時,在普通方法中,指向父類的 prototype
原型,所以不在原型 prototype
上的屬性和方法不能夠經過 super
調用。
class A { constructor() { this.a = 3; } p() {return 2;} } A.prototype.m = 6; class B extends A { constructor() { super(); console.log(super.a); // undefined console.log(super.p()); // 2 console.log(super.m); // 6 } } new B();
在子類普通方法中經過 super
調用父類方法時,方法內部的 this
指向當前的子類實例。
class A { constructor() { this.x = 'a'; } aX() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 'b'; } bX() { super.aX(); } } (new B()).bX() // 'b'
在靜態方法中,指向父類
class A { static m(msg) { console.log('static', msg); } m(msg) { console.log('instance', msg); } } class B extends A { static m(msg) { super.m(msg); } m(msg) { super.m(msg); } } B.m(1); // "static" 1 (new B()).m(2) // "instance" 2
在子類靜態方法中經過 super
調用父類方法時,方法內部的 this
指向當前的子類,而不是子類的實例。
因爲對象老是繼承其它對象的,因此能夠在任意一個對象中,使用 super
關鍵字,指向的是該對象的構造函數的 prototype
原型。
let obj = { m() { return super.constructor.name; } }; obj.m(); // Object
注意,使用 super
的時候,必須顯式的指定是做爲函數仍是做爲對象使用,不然會報錯。
class B extends A { m() { console.log(super); } } // SyntaxError: 'super' keyword unexpected here
父類的靜態方法,能夠被子類繼承。
class Person { static sayHi() { return 'hello'; } } class Teacher extends Person { } Teacher.sayHi() // "hello"
在子類的 static
內部,能夠從 super
對象上調用父類的靜態方法。
class Teacher extends Person { static sayHi() { super.sayHi(); } } Teacher.sayHi() // "hello"
Class
內部調用 new.target
,返回當前 Class
。且子類繼承父類時,new.target
會返回子類。所以利用這個特色,能夠寫出不能獨立使用必須繼承後才能使用的類。
class Shape { constructor() { if(new.target === Shape) { throw new Error('本類不能實例化'); } } } class Circle extends Shape { constructor(radius) { super(); console.log('ok'); } } let s = new Shape(); // 報錯 let cir = new Circle(6); // 'ok'