深入理解ES6 Class:从语法糖到底层原理

作者:木棉

时间:2026-10-25

浏览:128

本文深入解析ES6中class关键字的本质,指出其仅为构造函数与原型链的语法糖。详细讲解类的定义、constructor构造函数、实例方法、访问器(getter/setter)及静态方法的用法。重点阐述extends继承机制、super关键字在构造函数与实例方法中的多重用途,以及单继承限制下的混入(Mixin)方案。最后通过Babel转换示例,揭示class编译为ES5代码的底层实现逻辑及纯函数标记对Tree-shaking优化的意义。

文章目录

前言

在前文已介绍过利用构造函数配合原型链来实现JavaScript中的继承,从实现步骤来看相对繁琐。ES6引入了class关键字,允许开发者以类似其他面向对象语言的语法直接定义类。尽管使用class定义的类在语法上更为简洁,但其底层本质依然是构造函数与原型链的语法糖。接下来将全面解析class的具体用法与原理。

1.类的定义

使用class关键字定义类时,主要有两种定义方式:

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

2.类的构造函数

观察上述class定义类的代码可以发现,类定义本身并不包含用于传递参数的括号。若希望在实例化对象时向类传递参数,则需要使用类的构造函数constructor。

每个类都只能拥有一个constructor方法,若定义多个则会抛出异常;

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  constructor() {}
}

前言

当使用new操作符实例化类时,会自动调用该类的constructor方法,并返回一个对象(关于new操作符调用函数时的默认步骤,前文已有说明);

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
}

const p = new Person('curry', 30)
console.log(p) // Person { name: 'curry', age: 30 }

3.类的实例方法

在构造函数中实现方法继承时,通常将方法挂载到构造函数的原型上。而在class定义的类中,可以直接在类体内定义方法,class最终仍会将这些方法放置到原型上,供多个实例共享使用。

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log(this.name + 'is eating.')
  }

  running() {
    console.log(this.name + 'is running.')
  }
}

4.类的访问器方法

在使用Object.defineProperty()方法控制对象属性时,可以在数据属性描述符中使用setter和getter函数。在class定义的类中,同样支持使用这两种访问器方法。

class Person {
  constructor(name, age) {
    this.name = name
    this._age = 30 // 使用_定义的属性表示为私有属性,不可直接访问
  }

  get age() {
    console.log('age被访问')
    return this._age
  }

  set age(newValue) {
    console.log('age被设置')
    this._age = newValue
  }
}

const p = new Person('curry', 30)
console.log(p) // Person { name: 'curry', _age: 30 }
p.age // age被访问
p.age = 24 // age被设置
console.log(p) // Person { name: 'curry', _age: 24 }

5.类的静态方法

所谓类的静态方法,是指该方法不由实例对象调用,而是直接挂载在类本身上的方法,可通过类名直接访问。使用static关键字即可定义静态方法。

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  static foo() {
    console.log('我是Person类的方法')
  }
}

Person.foo() // 我是Person类的方法

6.类的继承

6.1.extends关键字

在ES6之前,实现继承并不便捷。ES6增加了extends关键字,能够更方便地帮助开发者实现类的继承。

实现Student子类继承自Person父类的示例如下:

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log(this.name + ' is eating.')
  }
}

class Student extends Person {
  constructor(sno) {
    this.sno = sno
  }

  studying() {
    console.log(this.name + ' is studying.')
  }
}

那么子类应如何使用父类的属性和方法呢?

6.2.super关键字

使用super关键字可以在子类构造函数中调用父类的构造函数,但必须在子类构造函数中使用this或返回默认对象之前调用super。

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log(this.name + ' is eating.')
  }
}

class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

  studying() {
    console.log(this.name + ' is studying.')
  }
}

const stu = new Student('curry', 30, 101111)
console.log(stu) // Student { name: 'curry', age: 30, sno: 101111 }
// 父类的方法可直接调用
stu.eating() // curry is eating.
stu.studying() // curry is studying.

然而,super关键字的用途不仅限于此,它通常可以在以下三个地方使用:

  • 子类的构造函数中(上述用法);
  • 实例方法中:子类不仅可以重写父类中的实例方法,还可以通过super关键字复用父类实例方法中的逻辑代码;
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log(this.name + ' is eating.')
  }

  parentMethod() {
    console.log('父类逻辑代码1')
    console.log('父类逻辑代码2')
    console.log('父类逻辑代码3')
  }
}

class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

  // 直接重写父类eating方法
  eating() {
    console.log('Student is eating.')
  }

  // 重写父类的parentMethod方法,并且复用逻辑代码
  parentMethod() {
    // 通过super调用父类方法,实现复用
    super.parentMethod()

    console.log('子类逻辑代码4')
    console.log('子类逻辑代码5')
    console.log('子类逻辑代码6')
  }
}

前言

静态方法中:用法与实例方法的方式一致;

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  static parentMethod() {
    console.log('父类逻辑代码1')
    console.log('父类逻辑代码2')
    console.log('父类逻辑代码3')
  }
}

class Student extends Person {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

  // 重写父类的parentMethod静态方法,并且复用逻辑代码
  static parentMethod() {
    // 通过super调用父类静态方法,实现复用
    super.parentMethod()

    console.log('子类逻辑代码4')
    console.log('子类逻辑代码5')
    console.log('子类逻辑代码6')
  }
}

Student.parentMethod()

前言

6.3.继承内置类

extends关键字不仅可以实现对我们自定义父类的继承,还可以继承JavaScript提供的内置类,从而对内置类的功能进行扩展。

例如,在Array类上扩展两个方法,一个方法获取指定数组的第一个元素,另一个方法获取数组的最后一个元素:

class myArray extends Array {
  firstItem() {
    return this[0]
  }

  lastItem() {
    return this[this.length - 1]
  }
}

const arr = new myArray(1, 2, 3)
console.log(arr) // myArray(3) [ 1, 2, 3 ]
console.log(arr.firstItem()) // 1
console.log(arr.lastItem()) // 3

7.类的混入

何为类的混入?在上述演示代码中,均只实现了子类继承自一个父类,因为JavaScript的类仅支持单继承,无法继承自多个类。若非要实现继承自多个类,则可引入混入(Mixin)的概念。

查看JavaScript中通过代码如何实现混入效果:

// 封装混入Animal类的函数
function mixinClass(BaseClass) {
  // 返回一个匿名类
  return class extends BaseClass {
    running() {
      console.log('running...')
    }
  }
}

class Person {
  eating() {
    console.log('eating...')
  }
}

class Student extends Person {
  studying() {
    console.log('studying...')
  }
}

const NewStudent = mixinClass(Student)
const stu = new NewStudent
stu.running() // running...
stu.eating() // eating...
stu.studying() // studying...

混入的实现一般不常用,因为参数传递较为困难,局限性较大,在JavaScript中单继承已足够使用。

8.class定义类转ES5

上文介绍了ES6中类的各种使用方法,极大便利了我们对类的使用。在日常开发中,编写的ES6代码均会被Babel解析为ES5代码,以适配低版本浏览器。那么使用ES6编写的类被编译成ES5语法会呈现何种形态?通过Babel官网的在线转换工具可以清晰看到ES6语法转换为ES5后的样子。

  • 首先通过执行自调用函数得到一个Person构造函数;
  • 定义的实例方法和类方法会分别收集到一个数组中,便于后续直接调用函数进行遍历添加;
  • 判断方法类型:如果是实例方法就添加到Person原型上,是类方法直接添加到Person上;
  • 因此,class定义类的本质仍是通过构造函数加原型链,class只是一种语法糖;

前言

这里可以提出一个小问题:定义在constructor外的属性最终会被添加到哪里?它们仍会被添加到类的实例化对象上,因为ES6对这样定义的属性进行了单独处理。

class Person {
  message = 'hello world'

  constructor(name, age) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log(this.name + ' is eating.')
  }

  static personMethod() {
    console.log('personMethod')
  }
}

const p = new Person('curry', 30)
console.log(p) // Person { message: 'hello world', name: 'curry', age: 30 }

前言

扩展:在上图中通过Babel转换后的代码中,定义的Person函数前有一个/*#__PURE__*/,那么这个有什么作用呢?

  • 实际上这个符号将函数标记为了纯函数,在JavaScript中纯函数的特点是没有副作用,不依赖于其他东西,独立性很强;
  • 在使用webpack构建的项目中,通过Babel转换后的语法更有利于webpack进行tree-shaking,未使用的纯函数会直接在打包时被压缩掉,从而达到减小包体积的效果;

总结

到此这篇关于JavaScript面向对象之深入了解ES6的class的文章就介绍到这了,更多相关了解ES6的class内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章