Class

coderljw 2024-10-13 JS 基础
  • JS 基础
  • Class
大约 3 分钟

# 1. class

  • ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

  • constructor 必须有,未显式定义时 JavaScript 引擎会默认添加,默认返回实例对象(this)。

  • class 声明的方法默认定义在原型链 prototype 上,不可枚举。

class XueDao {
  wife = '南宫仆射'
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  rank(ranking) {
    return `天下第${ranking}${this.name}`
  }
}

typeof XueDao // => 'function'

const xfq = new XueDao('徐扶墙', 17) // => {wife: '南宫仆射', name: '徐扶墙', age: 17}

xfq.rank('二') // => '天下第二徐扶墙'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const xfq = new (class XueDao {
  constructor(name) {
    this.name = name
    return {}
  }
})('徐扶墙') // => {}

// 定义了 constructor 返回值为空对象
xfq instanceof XueDao // => false
1
2
3
4
5
6
7
8
9

# 2. static

  • 静态属性/方法(定义在类上)。
class XueDao {
  static daughter = '柿子'
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  static rank(ranking) {
    return `天下第${ranking}徐扶墙`
  }
}

// 与 static 等价
XueDao.weapons = '双刀绣冬春雷'

XueDao.daughter // => '柿子'
XueDao.rank('二') // => '天下第二徐扶墙'
XueDao.weapons // => '双刀绣冬春雷'

const xfq = new XueDao('徐扶墙', 17) // => {name: '徐扶墙', age: 17}
xfq.daughter // => undefined
xfq.rank // => undefined
xfq.weapons // => undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 3. #

  • 私有属性/方法(只能在类内部访问)。
class XueDao {
  #daughter = '柿子'
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  #rank(ranking) {
    return `天下第${ranking}${this.name}`
  }
  versailles() {
    return `${this.#rank('二')} -> ${this.#daughter}`
  }
}

XueDao.#daughter  // => Uncaught SyntaxError
XueDao.#rank()  // => Uncaught SyntaxError

const xfq = new XueDao('徐扶墙', 17) // => {name: '徐扶墙', age: 17, #rank: ƒ, #daughter: '柿子'}
xfq.#daughter // => Uncaught SyntaxError
xfq.#rank() // => Uncaught SyntaxError
xfq.versailles() // => '天下第二徐扶墙 -> 柿子'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4. extends

  • 通过 extends 关键字实现继承,要比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

  • 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象,必须在 super 之后才能使用 this。

  • super() 作为函数只能在子类 constructor 中使用,内部 this 指向子类实例。super 作为对象调用,在普通方法中,指向父类的原型对象;在静态方法中,指向父类(super - 阮一峰 (opens new window))。

class XueDao {
  daughter = '太监总管'
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  show() {
    return this.daughter
  }
}

class BeiLiang extends XueDao {
  daughter = '柿子'
  constructor(name, age, wife) {
    super(name, age)
    this.wife = wife
  }
  versailles() {
    return super.show()
  }
}

const xfq = new BeiLiang('徐扶墙', 17, '南宫仆射') // => {name: '徐扶墙', age: 17, wife: '南宫仆射'}
xfq.versailles() // => '柿子'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 5. Mixin

  • 混合继承。
function mix(...mixins) {
  class Mix {
    constructor(...args) {
      for (const mixin of mixins) {
        copyProperties(this, new mixin(...args), true) // 拷贝实例属性
      }
    }
  }

  for (const mixin of mixins) {
    copyProperties(Mix, mixin) // 拷贝静态属性
    copyProperties(Mix.prototype, mixin.prototype) // 拷贝原型属性
  }

  return Mix
}

function copyProperties(target, source, allowName = false) {
  const excludeKeys = ['constructor', 'prototype', 'name']
  if (allowName) excludeKeys.pop()
  for (const key of Reflect.ownKeys(source)) {
    if (!excludeKeys.includes(key)) {
      const desc = Object.getOwnPropertyDescriptor(source, key)
      Object.defineProperty(target, key, desc)
    }
  }
}

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

class Bar {
  daughter = '柿子'
  constructor(wife) {
    this.wife = wife
  }
  versailles() {
    return this.daughter
  }
}

class XueDao extends mix(Foo, Bar) {
  rank(ranking) {
    return `天下第${ranking}${this.name}`
  }
}

const xfq = new XueDao('徐扶墙', 17, '南宫仆射') // => {name: '徐扶墙', age: 17, daughter: '柿子', wife: '徐扶墙'}
xfq.rank('二') // => '天下第二徐扶墙'
xfq.versailles() // => '柿子'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 6. @

  • 装饰器只能用于类,不能用于函数,因为存在函数提升(提案阶段,可搭配 babel 使用,目前很多库都已使用)。
import { decoratorClass, readonly, debounce } from 'decorator'

// 装饰类
@decoratorClass
class XueDao {
  // 装饰属性
  @readonly wife = '南宫仆射'
  // 装饰方法
  @debounce(1000)
  search() {}
}
1
2
3
4
5
6
7
8
9
10
11
export const decoratorClass = _class => {
  _class.age = 17
}

export const readonly = (target, name, descriptor) => {
  descriptor.writable = false
  return descriptor
}

export const debounce = wait => {
  const debounceFn = (fn, ms = 500) => {
    let timeoutId
    return function(...args) {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => fn.apply(this, args), ms)
    }
  }
  return function(target, name, descriptor) {
    descriptor.value = debounceFn(descriptor.value, wait)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
以父之名
周杰伦.mp3