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会报错
相关文章
相关标签/搜索