关于Javascript中的类

ES6新引入class关键字具有正式定义类的能力,表面上看起来支持正式的面向对象编程,实际上背后使用的仍然是原型和构造函数的概念。

一、类的定义

定义类的两种方式:类声明和类表达式。

// 类声明
class Person {}
// 类表达式
const Person = class {}

与函数定义不同的是,函数声明可以提升,类定义不能

console.log(ClassDeclaration) // ReferenceError: Cannot access 'ClassDeclaration' before initialization
class ClassDeclaration {}
console.log(ClassDeclaration) // [class ClassDeclaration]

另一个跟函数声明不同的是,函数受函数作用域限制,类受块作用域限制。

类的构成

类可以包含构造函数、实例方法、获取函数、设置函数和静态方法。类定义中的代码都在严格模式下进行。以下定义类的方式都有效:

// 空类定义
class Foo {}
// 有构造方法的类
class Bar {
  constructor () {}
}
// 有获取函数的类
class Baz {
  get myBaz() {}
}
// 有静态方法的类
class Qux {
  static myQux() {}
}

二、类构造函数

当使用new操作符创建类的实例时,类中定义的constructor函数会被调用。

class Person {
  constructor () {
    console.log('constructor invoked')
  }
}
let per1 = new Person() // 'constructor invoked'
  • 默认情况下,类构造函数会在执行之后返回this对象,该对象被用作实例化的对象。
  • 类构造函数与普通构造函数的区别:调用类构造函数必须使用new操作符。 普通构造函数如果不使用new调用,会以全局的this(通常是window)作为内部对象。
  • 把类当成特殊函数:通过typeof可以检测类标识符,表明它是一个函数:
console.log(typeof Person) // function
  • 类标识符有prototype属性,而这个原型也有一个constructor属性指向类自身。
console.log(Person === Person.prototype.constructor) // true
  • 在类的上下文中,类本身在使用new调用时就会被当作构造函数,而类中定义的constructor方法不会被当作构造函数。但是如果在创建实例时将类构造函数当作普通构造函数使用,instanceof结果会不一样:
let per1 = new Person()
console.log(per1 instanceof Person) // true
console.log(per1.constructor === Person) // true
console.log(per1 instanceof Person.constructor) // false

let per2 = new Person().constructor
console.log(per2 instanceof Person) // false
console.log(per2.constructor === Person) // false
console.log(per2 instanceof Person.constructor) // true

三、实例、原型和类成员

类中可以定义存在于实例上的成员,存在于原型上的成员,和存在于类本身的成员。

实例成员

每一个实例都对应一个唯一的成员对象,所有成员都不会在原型上共享:

class Person {
  constructor () {
    this.name = new String('Mike')
    this.color = ['red','green']
  }
}
let per1 = new Person()
let per2 = new Person()
console.log(per1.name === per2.name) // false
console.log(per1.color === per2.color) // false
原型方法与访问器

在类块中定义的所有内容都会定义在类的原型上

class Person {
  sayHi () {
    console.log('Hello')
  }
}
Person.prototype.sayHi()

可以把方法定义在类构造函数中或者类块中,但不能给原型添加原始值或对象作为成员数据:

class Person {
  name: 'mike' // 报错
}

类定义也支持获取和设置访问器:

class Person {
  set name(newName) {
    this.name_ = newName
  }
  get name() {
    return this.name_
  }
}
静态类方法

可以在类上定义静态方法,每个类中只能有一个静态成员。在类中使用static关键字,在静态成员中,this引用类自身。

class Person {
  static sayHi(){
    console.log('Hello')
  }
}
Person.sayHi() // 'Hello'

四、继承

ES6中原生支持了类继承机制,但背后使用的依旧是原型链。使用extends关键字,可以继承任何拥有[[Constructor]]和原型的对象。不仅可以继承一个类,也能继承一个构造函数。

// 继承类
class Person {}
class Student extends Person {}
let stu1 = new Student()
console.log(stu1 instanceof Person) // true
console.log(stu1 instanceof Student) // true

// 继承普通构造函数
function Person () {}
class Student extends Person {}
let stu1 = new Student()
console.log(stu1 instanceof Person) // true
console.log(stu1 instanceof Student) // true

派生类都会通过原型链访问到类和原型上定义的方法,this的值会反映调用相应的方法的实例或者类:

class Person {
  // 原型方法
  goto(place) {
    console.log(place, this)
  }
  // 类自身方法
  static comeback(place) {
    console.log(place, this)
  }
}
class Student extends Person {}
let stu = new Student()
stu.goto('London') // London Student {}
Student.comeback('NewYork') // NewYork [class Student extends Person]

派生类的方法可以通过super关键字引用它们的原型。关于super的用法:

  • 只能在派生类构造函数和静态方法中使用。
  • 不能单独引用,要么用它调用构造函数,要么引用静态方法
class Person {}
class Student extends Person {
  constructor () {
    super // 报错
  }
}
  • 调用super()会调用父类构造函数,并将返回的实例赋值给this
class Person {}
class Student extends Person {
  constructor () {
    super()
    console.log(this instanceof Person)
  }
}
let stu = new Student() // true
  • super()的行为如同调用构造函数,如果需要给父类构造函数传参,需手动传入
  • 如果没有定义类构造函数,在实例化派生类时会调用super(),而且会传入所有传给派生类的参数
class Person {
  constructor (arg) {
    console.log(arg)
  }
}
class Student extends Person {}
let stu = new Student('Mike') // 'Mike'
  • 在类构造函数中,不能在调用super()之前引入this(原因可以联系前面说到的this的值是由父类构造函数返回的实例,而父类函数的调用需要super()
class Person {}
class Student extends Person {
  constructor () {
    console.log(this) // 报错
  }
}
let stu = new Student()
  • 如果在派生类中显示定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象
class Person {}
class Student extends Person {
  constructor () {
    super()
  }
}
class Work extends Person {
  constructor () {
    return {}
  }
}
抽象基类

抽象基类:可供其他类继承,但本身不会被实例化。通过new.target来实现。new.target保存通过new关键字调用的类或函数。通过在实例化时检测new.target是不是抽象基类,可以阻止对抽象基类的实例化:

class Person {
  constructor () {
    console.log(new.target)
    if (new.target === Person){
      throw new Error('Person cannot be directly instantiated')
    }
  }
}
class Student extends Person {}
new Student() // [class Student extends Person]
new Person() // [class Person]
// Person cannot be directly instantiated

通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法。因为原型方法在调用类构造函数之前就已经存在了,所以通过this关键字来检查相应的方法。

class Person {
  constructor () {
    if (new.target === Person){
      throw new Error('Person cannot be directly instantiated')
    }
    if (!this.foo){
      throw new Error('Inheriting class must define foo()')
    }
    console.log('success!')
  }
}
class Student extends Person {
  foo () {
    console.log('foo')
  }
}
class Work extends Person { }
new Student() // success!
new Work() // Inheriting class must define foo()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值