本系列屬於阮一峯老師所著的ECMAScript 6 入門學習筆記javascript
// 生成實例對象的傳統方法是經過構造函數 function Point(x,y){ this.x = x this.y = y } Point.prototype.toString = function(){ return '(' + this.x + ',' + this.y + ')' } var p = new Point(1,2) // 爲了讓寫法更接近面嚮對象語言,引入Class(類)這個概念 class Point{ constructor(x,y){ this.x = x this.y = y } toString(){ return '(' + this.x + ',' + this.y + ')' } } // 因爲類的方法都定義在prototype對象上,因此類的新方法能夠添加在prototype對象上面 class Point{ constructor(){ // ... } } Object.assign(Point,prototype,{ toString(){}, toValue(){} }) // prototype對象的constructor屬性,直接指向類自己,與ES5行爲一致 Point.prototype.constructor === Point // true // 類內部定義的方法,都是不可枚舉的(non-enumerable) class Point{ constructor(x,y){ // ... } toString(){ // ... } } Object.keys(Point.prototype) // [] Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] // 這一點與ES5不一樣,ES5中對象內部定義的方法是可枚舉的 var Point = function(x,y){ // ... } Point.prototype.toString = function(){ // ... } Object.keys(Point.prototype) // ["toString"] Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] // 類的屬性名,能夠採用表達式 let methodName = 'getArea' class Square { constructor(length){ // ... } [methodName](){ // ... } }
類和模塊的內部,默認就是嚴格模式。java
考慮到將來全部的代碼,其實都是運行在模塊之中,因此ES6實際把整個語言升級到嚴格模式。es6
constructor
方法是類的默認方法,經過new
命名生成對象實例時,自動調用該方法。函數
// constructor方法默認返回實例對象(即this),徹底能夠指定返回另一個對象 class Foo{ constructor(){ return Object.create(null) } } new Foo() instanceof Foo // false // 類必須用new調用,不然會報錯。這是和普通構造函數的一個主要區別
// 與ES5同樣,實例屬性除非顯式定義在其本省(即定義在this對象上),不然都是定義在原型上 class Point{ constructor(x,y){ this.x = x this.y = y } toString(){ return '(' + this.x + ',' + this.y + ')' } } var point = new Point(2,3) point.toString() // (2,3) point.hasOwnProperty('x') // true point.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true // 與ES5同樣,共享一個原型對象 var p1 = new Point(1,2) var p2 = new Point(2,3) p1.__proto__ === p2.__proto__ // true // 這也意味着,能夠經過實例的__proto__屬性爲類添加方法 p1.__proto__.printName = function () { return 'Oops' } p1.printName() // 'Oops' p2.printName() // 'Oops' var p3 = new Point(4,2) p3.printName() // 'Oops' // 使用實例的__proto屬性改寫原型必須至關謹慎,由於這會改變類的原始定義,影響到全部實例,不推薦使用。
// 與函數同樣,類也可使用表達式的形式定義 const MyClass = class Me { getClassName(){ return Me.name } } // 須要注意的是,這個類的名字是MyClass而不是Me,Me只在Class的內部代碼可用,指代當前類 let inst = new MyClass() inst.getClassName() // Me Me.name // ReferenceError: Me is not defined // 內部未使用的話能夠省略Me const MyClass = class { /* ... */ } // 利用Class表達式,能夠寫出當即執行的Class let person = new class{ constructor(name){ this.name = name } sayName(){ console.log(this.name) } }('Angus') person.sayName() // 'Angus'
// 與ES5徹底不一樣的是,類不存在變量提高 new Foo() // ReferenceError class Foo {} // 該規定與類的繼承有關,必須保證子類在父類以後定義
// 私有方法是常見需求,可是ES6不提供,只能經過變通方法模擬實現 // 利用命名區別私有方法(加_),可是不保險,類的外部依舊能夠調用這個方法 class Widget{ // 公有方法 foo(baz){ this._bar(baz) } // 私有方法 _bar(baz){ return this.snaf = baz } } // 將私有方法移出模塊,由於模塊內部的全部方法都是對外可見的 class Widget{ foo(baz){ bar.call(this,baz) } } function bar(baz){ return this.snaf = baz } // 利用Symbol值的惟一性,將私有方法的名字命名爲一個Symbol值,使第三方沒法獲取 const bar = Symbol('bar') const snaf = Symbol('snaf') export default class myClass{ // 公有方法 foo(baz){ this[bar](baz) } // 私有方法 [baz](baz){ return this[snaf] = baz } }
與私有方法同樣,ES6不支持私有屬性。目前有一個提案,爲class加私有屬性,方法是在屬性名以前,使用#表示。學習
class Point{ #x constructor(x=0){ #x = +x // 寫成this.#x亦可 } get x() { return #x } set x(value) { #x = +value } }
// 類的方法內部若是有this,則默認指向類的實例。可是一旦單獨使用該方法,極可能報錯 class Logger { printName(name = 'there'){ this.print(`Hello ${name}`) } print(text){ console.log(text) } } const logger = new Logger() const { printName } = logger printName() // TypeError: Cannot read property 'prin' of undefined // 若是單獨使用,this會指向該方法運行時所在的環境,由於找不到print方法而報錯 // 解決辦法一:在構造方法中綁定this class Logger { constructor(){ this.printName = this.printName.bind(this) } } // 解決辦法二:使用箭頭函數 class Logger{ constructor(){ this.printName = (name = 'there') => { this.print(`Hello ${name}`) } } }
// 本質上,ES6的類只是ES5構造函數的一層包裝,因此函數的許多特性都被class繼承了,包括name屬性 class Point {} // name屬性老是返回緊跟在class關鍵字後面的類名 Point.name // 'Point'
// 與ES5同樣,在類的內部可使用get和set關鍵字,對某屬性設置存值函數和取值函數,攔截該屬性的存取行爲 class MyClass{ constructor(){ // ... } get prop(){ return 'getter' } set prop(value){ console.log('setter:' + value) } } let inst = new MyClass() inst.prop = 123 // setter: 123 inst.prop // 'getter' // 存值函數和取值函數是設置在屬性的Descriptor對象上的
// 若是在方法以前加上星號(*),就表示該方法是一個Generator函數 class Foo{ constructor(...args){ this.args = args } *[Symbol.iterator](){ for (let arg of this.args) { yield arg } } } // Symbol.iterator方法返回一個Foo類的默認遍歷器,for...of循環會自動調用這個遍歷器 for (let x of new Foo('Hello','world')) { console.log(x) } // Hello // world
// 至關於實例的原型,全部在類中定義的方法,都被會實例繼承。若是在一個方法前加上static關鍵字,就表示該方法不會被繼承,而是直接經過類來調用,稱爲「靜態方法」 class Foo { static classMethod() { return 'Hello' } } Foo.classMethod() // 'Hello' var foo = new Foo() foo.classMethod() // TypeError: foo.classMethod is not a function // 若是靜態方法中包含this關鍵字,這個this指的是類,而不是實例 class Foo { static bar(){ this.baz() } static baz(){ console.log('hello') } baz(){ console.log('world') } } // this指的是Foo類,而不是Foo實例,等同於調用Foo.baz,另外靜態方法能夠與非靜態方法重名 Foo.bar() // 'hello' // 父類的靜態方法,能夠被子類繼承 class Foo { static classMethod(){ return 'hello' } } class Bar extends Foo {} Bar.classMehod() // 'hello' // 靜態方法也能夠從super對象上調用 class Foo { static classMethod(){ return 'hello' } } class Bar extends Foo { static classMethod(){ return super.classMethod() + ',too' } } Bar.classMethod() // 'hello,too'
// 該屬性通常用在構造函數中,返回new命令做用於的那個構造函數。若是構造函數不是經過new命令調用的,new.target會返回undefined,所以這個屬性能夠用來肯定構造函數是怎麼調用的 function Person(name){ if(new.target !== undefined){ this.name = name }else{ throw new Error('必須使用new命令生成實例') } } // 另一種寫法 function person(name){ if(new.target === Person){ this.name = name }else{ throw new Error('必須使用new命令生成實例') } } var person = new Person('Angus') // 正確 var notAPerson = Person.call(person,'Angus') // 報錯 // Class內部調用new.target,返回當前Class class Rectangle{ constructor(length,width){ console.log(new.target === Rectangle) this.length = length this.width = width } } var obj = new Rectangle(3,4) // true // 子類繼承父類時,new.target會返回子類 class Square extends Rectangle { constructor(length){ super(length,length) } } var obj = new Square(3) // false // 利用這個特色能夠寫出不能獨立使用,必須繼承後使用的類 class Shape{ constructor(){ if(new.target === Shape){ throw new Error('本類不能被實例化') } } } class Rectangle extends Shape { constructor(length,width){ super() // ... } } var x = new Shape() // 報錯 var y = new Rectangle(3,4) // 正確 // 注意,在函數外部,使用new.target會報錯