ES 6 class 相关用法

2018-11-22  本文已影响0人  YeLqgd

上篇笔记简单对比了,从 ES 5 到 ES 6 类的写法的变化:1、增加了 class 语法糖,而不必再繁琐地先写构造函数然后再在构造函数的 prototype 上添加属性或方法;2、通过 static 关键字增加了静态方法的写法。以及留下一个疑问:那些介绍 ES 6 的书上写的「ES 6 的继承机制与 ES5 完全不同,ES 6 的机制是先由父类创建 this 值,然后再由子类的 contructor 去修改这个值」,这个机制提现在代码实现上究竟是怎样的。我去试了下 Babel,然而看了半天发现 Babel 对 ES 6 的继承的转码实现本质上也依然是上篇笔记里提到的「寄生组合式继承」的加强版:将一些可重用的过程抽象成 _createClassdefineProperties_possibleConstructorReturn_inherits_classCallCheck 等方法,但本质上还是 ES 5 的继承机制。所以如果有谁知道 ES 6 的继承机制体现在代码上究竟是怎样,或者说能这种机制通过 ES 5 的代码没法实现,求告知。

下面稍微详细地介绍一下 ES 6 中类的相关用法,在此之前先了解下 classfunction 的不同:

console.log(A) // ReferenceError: A is not defined
class A {}
class A {
  method1() {
    console.log('I am not enumerable')
  }
}
console.log(Object.keys(A.prototype)) // []

function B() {}
B.prototype.method1 = function() {
  console.log('I am enumerable')
}
console.log(Object.keys(B.prototype)) // ['method1']
class A {}
A() // Uncaught TypeError: Class constructor A cannot be invoked without 'new'

Babel 中通过 _classCallCheck 这个方法实现此功能。

class A {
  constructor() {
    A = 'B'  // Uncaught SyntaxError: Identifier 'A' has already been declared
  }
}
A = B // 但在声明结束时可以修改

类表达式

我们知道函数除了可以声明生成以外还可以作为表达式,同样地,类也可以作为表达式,匿名或命名:

let Person = class {
  constructor(name) {
    this.name = name
  }
  
  sayName() {
    console.log(this.name)
  }
}
let person = new Person('chongErFei')
person.sayName // chongErFei

作为参数和返回值:

function extendsFunc(SubClass)  {
  return class SuperClass extends SubClass {
    // 省略 constructor 等价于 constructor(...args) { super(...args) }
    equippedFunc() {
      console.log('I am equipped by extendsFunc')
    }
  }
}

let EquippedPerson = extendsFunc(Person)
let ep = new EquippedPerson('mjmjxihrni')

ep.sayName() // mjmjxihrni
ep.equippedFunc() // I am equipped by extendsFunc

访问器属性、可计算成员名称

ES 5 中访问器属性需要 Object.defineProperty 来定义,需要写一串很繁琐的代码,而 ES 6 中可以通过直接在属性名前加 get 或是 set 来定义 gettersetter;其实访问器属性在普通的对象字面量就支持,可计算成员以及生成器方法也是,在类里就更不用说了:

let propertyName = 'name'
let methodName  = 'tellAge'
class Person {
  constructor(name, age) {
    this[propertyName] = name
    this.age = age
  }

  get upperCaseName() {
    return this.name.toUpperCase()
  }
  [methodName]() {
    console.log(this.age)
  }
}

let p = Person {name: "lll", age: 24}

p.upperCaseName // LLL
p[methodName] // 24

super

Symbol.species 属性

Symbol.species 是众多内部 Symbol 的一个,它是一个静态访问器属性,返回值是一个构造函数,ES 6 中 ArrayArrayBufferMapPromiseRegExpSetTyped arrays 等内建类型都有这个属性,如果在自定义的类中实现这个属性,那么它看起来可能是

class MyClass {
  static get [Symbol.species]() {
    return this
  }

  constructor(value) {
    this.value = value
  }

  clone() {
    return new this.constructor[Symbol.species](this.value)
  }
}

,这个属性在我们需要继承内建对象时有用,假如我们现在想构建一个以 Array 为基类的特殊数组:

class MyArray extends Array {
  //  ...
}
let items = new MyArray(1, 2, 3, 4),
    subItems = items.slices(1, 3)

console.log(items instanceof MyArray) // true
console.log(subItems instanceof MyArray) // true

正常情况下,Array.slice() 的返回值应该是一个 Array 类型的,现在它成了 MyArray 类型,这里我们就可以通过重写派生类的 Symbol.species 属性来使凡是调用基类的方法都使用 Array 的实例而不用 MyArray

class MyArray extends Array {
  static get [Symbol.species]() {
    return Array
  }
}
let items = new MyArray(1, 2, 3, 4),
    subItems = items.slices(1, 3)

console.log(items instanceof MyArray) // true
console.log(subItems instanceof MyArray) // false
console.log(subItems instanceof Array) // true

一般说来,只要想在类方法中调用 this.constructor,都应该用 this.constructor[Symbol.species] 从而可以让以此类为基类的派生类改写 [Symbol.species] 属性。

上一篇 下一篇

猜你喜欢

热点阅读