Class的基本語法

本系列屬於阮一峯老師所著的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方法

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屬性改寫原型必須至關謹慎,由於這會改變類的原始定義,影響到全部實例,不推薦使用。
Class表達式
// 與函數同樣,類也可使用表達式的形式定義
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的指向
// 類的方法內部若是有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}`)
    }
  }
}
name屬性
// 本質上,ES6的類只是ES5構造函數的一層包裝,因此函數的許多特性都被class繼承了,包括name屬性
class Point {}
// name屬性老是返回緊跟在class關鍵字後面的類名
Point.name // 'Point'
Class的取值函數(getter)和存值函數(setter)
// 與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對象上的
Class的Generator方法
// 若是在方法以前加上星號(*),就表示該方法是一個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
Class的靜態方法
// 至關於實例的原型,全部在類中定義的方法,都被會實例繼承。若是在一個方法前加上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.target屬性
// 該屬性通常用在構造函數中,返回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會報錯
相關文章
相關標籤/搜索