js 中的类(class)

本来打算介绍下 ts 中的类,但是转念一想还是先来说说 js 中的类吧。

定义类的方法

在 ES6 中,我们可以使用 class 关键字直接定义类。定义的方式类似于函数的定义方式,可以分为 2 种:

  1. 类声明(比较常见):
javascript 复制代码
// 例 1
class Animal {}
  1. 类的表达式:
javascript 复制代码
const Animal = class {}

小提示:如果通过 typeof 来查看例 1 中声明的类 Animal 的类型,console.log(typeof Animal) 结果会是 "function",这是因为 typeof 可能的返回值是固定的那么几个,里面没有 class。

类的特点

其实,class 定义类从本质上可以看成是 ES5 中的构造函数的语法糖,例 1 可以看成是定义了一个构造函数: function Animal() {},所以构造函数的那些特性类也有:

  • 类也有 prototype 属性;
  • 类的原型对象的 [[Prototype]] 属性默认也是指向 Object.prototype
javascript 复制代码
console.log(Animal.prototype.__proto__ === Object.prototype) // true
  • 类的原型对象也有 constructor 属性,默认指向类本身:
javascript 复制代码
console.log(Animal.prototype.constructor === Animal) // true
  • new 一个类的时候,同样会让生成的对象(最后返回作为实例对象)的隐式原型指向类的显示原型:
javascript 复制代码
const animal = new Animal()
console.log(animal.__proto__ === Animal.prototype) // true

不难发现,如果之前关于构造函数与原型、原型链那部分知识掌握好了,类的学习就会轻松许多。

类的构造方法(constructor)

在构造函数中,我们可以给函数传参,在函数中通过 this.xx = yy的形式最终让生成的实例拥有值为 yy 的 xx 属性。在类中,我们则可以通过定义构造函数的方式来实现。注意,构造函数只能定义 1 个,且函数名必须为 constructor

javascript 复制代码
// 例 2.1
class Animal {
  constructor(height, weight) {
    this.height = height
    this.weight = weight
  }
}

const animal = new Animal(2, 200)
console.log(animal) // Animal {height: 2, weight: 200}

构造函数 constructor 会在我们通过 new 创建类的实例时调用:const animal = new Animal(height, weight),即使我们像例 1 那样不自己手写定义,创建实例时还是会执行默认的constructor函数。执行构造函数时做的事情和 new 一个构造函数背后做的事情如出一辙:

  1. 在内存中创建一个新的空对象;
  2. 类的 prototype的值会被赋给第 1 步创建的对象的 [[Prototype]] 属性;
  3. 构造函数内部的 this,也会指向第 1 步创建的对象;
  4. 执行函数的内部代码(函数体);
  5. 如果构造函数没有返回非空对象,则返回第 1 步创建的对象(其实也就是 this)。

类的方法

类里面可以定义的方法分为 3 种:

1. 实例方法

实例属性的定义我们通过 constructor 函数解决了,那么实例的方法我们在类中是如何定义的呢?很简单,直接在类中定义即可,比如我们想给 Animal 类添加一个实例方法 eat

javascript 复制代码
// 例 2.2
class Animal {
  // ...
  eat() {
    console.log('我是 Animal 类的实例的方法')
  }
}

如此,eat 方法就会被添加到 Animal.prototype 上,可以打印 Animal 原型对象自身的所有属性来验证:

javascript 复制代码
console.log(Reflect.ownKeys(Animal.prototype)) // [ 'constructor', 'eat' ]

方法还有另外一种比较少见的写法如下:

javascript 复制代码
// 例 2.2.1
class Animal {
  // ...
  eat = () => {
    console.log('我是 Animal 类的实例的方法')
  }
}

注意第 4 行这里不是用 : 而是用 = 赋值的形式。

2. 访问器方法(存储器属性)

在类里面还可以通过 getter 和 setter 方法定义实例的属性,以便之后在获取或修改该属性时进行一些拦截操作:

javascript 复制代码
// 例 3
class Animal {
  constructor() {
    // ...
    this._flag = '我是个属性'
  }
  // ...
  get flag() {
    return this._flag
  }
  set flag(val) {
    this._flag = val
  }
}

如上,我们就给 Animal 的实例添加了属性 flag

javascript 复制代码
const animal = new Animal()
console.log(animal.flag) // 我是个属性

注意:

  • 虽然 flag 是个访问器方法,但其实是个属性(存储器属性),获取的时候 animal.flag 是不能加括号的。
  • 如果没有设置 set 方法,那么在 js 中,执行 animal.flag = 'xxx' 并不会报错(ts 会报错),但也不会生效。

3. 静态方法

我们可以通过 static 关键字定义静态方法。

javascript 复制代码
// 例 4.1
class Animal {
  // ...
  static getFlag() {
    console.log('我是 Animal 类的静态方法')
  }
}

不同于实例方法,静态方法直接通过类调用:Animal.getFlag(),而无需生成实例对象。比如 Date.now()now() 方法就是 Date 类的一个静态方法

静态属性

虽然 ES6 是不支持静态属性的,但我们可以通过 static 配合访问器方法来定义类的静态属性。

javascript 复制代码
// 例 4.2
class Animal {
 // ...
  static get flag() {
    return '我是 Animal 类的静态属性'
  }
}

这样我们就可以通过 Animal.flag获取到 Animal 类的静态属性 flag 了。注意和例 3 进行对比,例 4.2 中我们不能在第 5 行返回 this._flag,因为当我们通过 Animal.flag 获取 flag 时,this 指向的是 Animal,Animal 本身是没有 _flag属性的。

相关推荐
客场消音器3 分钟前
我用两周半 Vibe Coding 做了一个前额叶训练的微信小程序
前端·javascript·后端
铁皮饭盒1 小时前
成为AI全栈 - 第4课:Drizzle ORM SQLite Elysia 数据库实战
前端·后端
ascarl20101 小时前
Linux.do 帖子整理:AI 调用 Chrome DevTools 调试前端页面
linux·前端·人工智能
DanCheOo1 小时前
开源 | 我是怎么用 ai-memory 让 Cursor 每次开新对话都自动知道项目背景的
前端·人工智能·ai·ai编程
MPGWJPMTJT2 小时前
告别手动切换 Node 版本:从 nvm 迁移到 Volta
前端
Apifox2 小时前
Apifox 近期更新|AI Agent Debugger、A2A Debugger、Postman API 导入、Ask AI 侧边栏对话
前端·人工智能·后端
嗷o嗷o2 小时前
Android 前台服务为什么越来越难用了?很多问题不是限制多,而是你任务模型就不该用 FGS
前端
摇滚侠2 小时前
软件开发外包项目组,如何提高代码质量和开发效率
java·开发语言·前端·ide·intellij-idea
不考研当牛马2 小时前
HTML CSS 新手大全初学者必看 (含有部分 JavaScript)
javascript·css·html
卷帘依旧2 小时前
Promise链式调用原理
前端·javascript