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属性的。

相关推荐
清云随笔13 分钟前
axios 实现 无感刷新方案
前端
鑫宝Code14 分钟前
【React】状态管理之Redux
前端·react.js·前端框架
忠实米线23 分钟前
使用pdf-lib.js实现pdf添加自定义水印功能
前端·javascript·pdf
pink大呲花25 分钟前
关于番外篇-CSS3新增特性
前端·css·css3
少年维持着烦恼.29 分钟前
第八章习题
前端·css·html
我是哈哈hh32 分钟前
HTML5和CSS3的进阶_HTML5和CSS3的新增特性
开发语言·前端·css·html·css3·html5·web
田本初1 小时前
如何修改npm包
前端·npm·node.js
明辉光焱1 小时前
[Electron]总结:如何创建Electron+Element Plus的项目
前端·javascript·electron
牧码岛2 小时前
Web前端之汉字排序、sort与localeCompare的介绍、编码顺序与字典顺序的区别
前端·javascript·web·web前端