文章首發於 我的博客前端
class是一個語法糖,其底層仍是經過 構造函數
去建立的。因此它的絕大部分功能,ES5 均可以作到。新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。git
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.sayName = function() { return this.name; } const xiaoming = new Person('小明', 18); console.log(xiaoming); 複製代碼
上面代碼用ES6
的class
實現,就是下面這樣es6
class Person { constructor(name, age) { this.name = name; this.age = age; } sayName() { return this.name; } } const xiaoming = new Person('小明', 18) console.log(xiaoming); // { name: '小明', age: 18 } console.log((typeof Person)); // function console.log(Person === Person.prototype.constructor); // true 複製代碼
constructor方法,這就是構造方法,this關鍵字表明實例對象。 類的數據類型就是函數,類自己就指向構造函數。github
定義類的時候,前面不須要加 function, 並且方法之間不須要逗號分隔,加了會報錯。編程
類的全部方法都定義在類的prototype屬性上面。微信
class A { constructor() {} toString() {} toValue() {} } // 等同於 function A () { // constructor }; A.prototype.toString = function() {}; A.prototype.toValue = function() {}; 複製代碼
在類的實例上面調用方法,其實就是調用原型上的方法。markdown
let a = new A(); a.constructor === A.prototype.constructor // true 複製代碼
constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。函數
class A { } // 等同於 class A { constructor() {} } 複製代碼
constructor方法默認返回實例對象(即this),徹底能夠指定返回另一個對象。oop
class A { constructor() { return Object.create(null); } } console.log((new A()) instanceof A); // false 複製代碼
實例的屬性除非顯式定義在其自己(即定義在this對象上),不然都是定義在原型上(即定義在class上)。學習
new A(); // ReferenceError class A {} 複製代碼
由於 ES6 不會把類的聲明提高到代碼頭部。這種規定的緣由與繼承有關,必須保證子類在父類以後定義。
{ let A = class {}; class B extends A {} } 複製代碼
上面的代碼不會報錯,由於 B繼承 A的時候,A已經有了定義。可是,若是存在 class提高,上面代碼就會報錯,由於 class 會被提高到代碼頭部,而let命令是不提高的,因此致使 B 繼承 A 的時候,Foo尚未定義。
類至關於實例的原型,全部在類中定義的方法,都會被實例繼承。 若是在一個方法前,加上 static 關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲"靜態方法"。
class A { static classMethod() { return 'hello'; } } A.classMethod(); console.log(A.classMethod()); // 'hello' const a = new A(); a.classMethod(); // TypeError: a.classMethod is not a function 複製代碼
A
類的classMethod
方法前有 static
關鍵字,代表這是一個靜態方法,能夠在 A
類上直接調用,而不是在實例上調用 在實例a
上調用靜態方法,會拋出一個錯誤,表示不存在改方法。
若是靜態方法包含this關鍵字,這個this指的是類,而不是實例。
class A { static classMethod() { this.baz(); } static baz() { console.log('hello'); } baz() { console.log('world'); } } A.classMethod(); // hello 複製代碼
靜態方法classMethod
調用了this.baz
,這裏的this
指的是A
類,而不是A
的實例,等同於調用A.baz
。另外,從這個例子還能夠看出,靜態方法能夠與非靜態方法重名。
父類的靜態方法,能夠被子類繼承。
class A { static classMethod() { console.log('hello'); } } class B extends A {} B.classMethod() // 'hello' 複製代碼
靜態屬性指的是 Class 自己的屬性,即Class.propName,而不是定義在實例對象(this)上的屬性。 寫法是在實例屬性的前面,加上static關鍵字。
class MyClass { static myStaticProp = 42; constructor() { console.log(MyClass.myStaticProp); // 42 } } 複製代碼
Class 能夠經過extends關鍵字實現繼承
class Animal {} class Cat extends Animal { }; 複製代碼
上面代碼中 定義了一個 Cat 類,該類經過 extends
關鍵字,繼承了 Animal 類中全部的屬性和方法。 可是因爲沒有部署任何代碼,因此這兩個類徹底同樣,等於複製了一個Animal類。 下面,咱們在Cat內部加上代碼。
class Cat extends Animal { constructor(name, age, color) { // 調用父類的constructor(name, age) super(name, age); this.color = color; } toString() { return this.color + ' ' + super.toString(); // 調用父類的toString() } } 複製代碼
constructor方法和toString方法之中,都出現了super關鍵字,它在這裏表示父類的構造函數,用來新建父類的this對象。
子類必須在 constructor 方法中調用 super 方法,不然新建實例就會報錯。 這是由於子類本身的this對象,必須先經過 父類的構造函數完成塑造,獲得與父類一樣的實例屬性和方法,而後再對其進行加工,加上子類本身的實例屬性和方法。若是不調用super方法,子類就得不到this對象。
class Animal { /* ... */ } class Cat extends Animal { constructor() { } } let cp = new Cat(); // ReferenceError 複製代碼
Cat 繼承了父類 Animal,可是它的構造函數沒有調用super方法,致使新建實例報錯。
若是子類沒有定義constructor方法,這個方法會被默認添加,代碼以下。也就是說,無論有沒有顯式定義,任何一個子類都有constructor方法。
class Cat extends Animal { } // 等同於 class Cat extends Animal { constructor(...args) { super(...args); } } 複製代碼
另外一個須要注意的地方是,es5 的構造函數在調用父構造函數前能夠訪問 this, 但 es6 的構造函數在調用父構造函數(即 super)前不能訪問 this。
class A { constructor(x, y) { this.x = x; this.y = y; } } class B extends A { constructor(x, y, name) { this.name = name; // ReferenceError super(x, y); this.name = name; // 正確 } } 複製代碼
上面代碼中,子類的constructor方法沒有調用super以前,就使用this關鍵字,結果報錯,而放在super方法以後就是正確的。
父類的靜態方法,也會被子類繼承。
class A { static hello() { console.log('hello world'); } } class B extends A { } B.hello() // hello world 複製代碼
super這個關鍵字,既能夠看成函數使用,也能夠看成對象使用
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)。
class A { constructor() { // new.target 指向正在執行的函數 console.log(new.target.name); } } class B extends A { constructor() { super(); } } new A() // A new B() // B 複製代碼
在super()
執行時,它指向的是子類B
的構造函數,而不是父類A的構造函數。也就是說,super()
內部的this
指向的是B
。
在普通方法中,指向父類的原型對象; 在靜態方法中,指向父類。
class A { p() { return 2; } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } } let b = new B(); 複製代碼
上面代碼中,子類B
當中的super.p()
,就是將super
看成一個對象使用。這時,super
在普通方法之中,指向A.prototype
,因此super.p()
就至關於A.prototype.p()
。
這裏須要注意,因爲super指向父類的原型對象,因此定義在父類實例上的方法或屬性,是沒法經過super調用的。
class A { constructor() { this.p = 2; } } class B extends A { get m() { return super.p; } } let b = new B(); b.m // undefined 複製代碼
上面代碼中,p是父類A實例的屬性,super.p就引用不到它。
若是屬性定義在父類的原型對象上,super
就能夠取到。
class A {} A.prototype.x = 2; class B extends A { constructor() { super(); console.log(super.x) // 2 } } let b = new B(); 複製代碼
上面代碼中,屬性x是定義在A.prototype
上面的,因此super.x
能夠取到它的值。
用在靜態方法之中,這時super將指向父類,而不是父類的原型對象。
class Parent { static myMethod(msg) { console.log('static', msg); } myMethod(msg) { console.log('instance', msg); } } class Child extends Parent { static myMethod(msg) { super.myMethod(msg); } myMethod(msg) { super.myMethod(msg); } } Child.myMethod(1); // static 1 const child = new Child(); child.myMethod(2); // instance 2 複製代碼
上面代碼中,super在靜態方法之中指向父類,在普通方法之中指向父類的原型對象。
另外,在子類的靜態方法中經過super
調用父類的方法時,方法內部的this
指向當前的子類,而不是子類的實例。
class A { constructor() { this.x = 1; } static print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; } static m() { super.print(); } } B.x = 3; B.m() // 3 複製代碼
上面代碼中,靜態方法B.m
裏面,super.print
指向父類的靜態方法。這個方法裏面的this
指向的是B
,而不是B
的實例。
構造函數
去建立的。class Person { constructor(name) { this.name = name } } const member = new Person("John") console.log(typeof member) 複製代碼
答案:object
解析: 類是構造函數的語法糖,若是用構造函數的方式來重寫Person類則將是:
function Person() { this.name = name } 複製代碼
經過new來調用構造函數,將會生成構造函數Person的實例,對實例執行typeof關鍵字將返回"object",上述狀況打印出"object"。
class Chameleon { static colorChange(newColor) { this.newColor = newColor return this.newColor } constructor({ newColor = 'green' } = {}) { this.newColor = newColor } } const freddie = new Chameleon({ newColor: 'purple' }) freddie.colorChange('orange') 複製代碼
答案:TypeError
解析: colorChange 是一個靜態方法。靜態方法被設計爲只能被建立它們的構造器使用(也就是 Chameleon),而且不能傳遞給實例。由於 freddie 是一個實例,靜態方法不能被實例使用,所以拋出了 TypeError 錯誤。
class Person { constructor() { this.name = "Lydia" } } Person = class AnotherPerson { constructor() { this.name = "Sarah" } } const member = new Person() console.log(member.name) 複製代碼
答案:"Sarah"
解析: 咱們能夠將類設置爲等於其餘類/函數構造函數。 在這種狀況下,咱們將Person設置爲AnotherPerson。 這個構造函數的名字是Sarah,因此新的Person實例member上的name屬性是Sarah。
最近發起了一個100天前端進階計劃,主要是深挖每一個知識點背後的原理,歡迎關注 微信公衆號「牧碼的星星」,咱們一塊兒學習,打卡100天。