上篇笔记简单对比了,从 ES 5 到 ES 6 类的写法的变化:1、增加了 class 语法糖,而不必再繁琐地先写构造函数然后再在构造函数的 prototype
上添加属性或方法;2、通过 static
关键字增加了静态方法的写法。以及留下一个疑问:那些介绍 ES 6 的书上写的「ES 6 的继承机制与 ES5 完全不同,ES 6 的机制是先由父类创建 this
值,然后再由子类的 contructor
去修改这个值」,这个机制提现在代码实现上究竟是怎样的。我去试了下 Babel,然而看了半天发现 Babel 对 ES 6 的继承的转码实现本质上也依然是上篇笔记里提到的「寄生组合式继承」的加强版:将一些可重用的过程抽象成 _createClass
、defineProperties
、_possibleConstructorReturn
、_inherits
、_classCallCheck
等方法,但本质上还是 ES 5 的继承机制。所以如果有谁知道 ES 6 的继承机制体现在代码上究竟是怎样,或者说能这种机制通过 ES 5 的代码没法实现,求告知。
下面稍微详细地介绍一下 ES 6 中类的相关用法,在此之前先了解下 class
与 function
的不同:
- 函数声明可以被提升,而 class 声明生效的机制与
let
一致:
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']
- 使用除关键字
new
以外的方式调用类都会报错:
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
来定义 getter
或 setter
;其实访问器属性在普通的对象字面量就支持,可计算成员以及生成器方法也是,在类里就更不用说了:
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
-
super
只能在派生类中使用,否则会报错 - 派生类的
constructor
中使用this
前,一定要先调用super()
-
super
在派生类中有两种含义,在派生类的constructor
中出现时代表父类的constructor
;而在实例方法中出现时表示父类的prototype
Symbol.species
属性
Symbol.species
是众多内部 Symbol
的一个,它是一个静态访问器属性,返回值是一个构造函数,ES 6 中 Array
、ArrayBuffer
、Map
、Promise
、RegExp
、Set
、Typed 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]
属性。
网友评论