类定义
ECMAScript 中没有正式的类这个类型。类(class)是 ECMAScript 中新的基础性语法糖结构,背后使用的仍然是原型和构造函数的概念。
// 类声明
class Person {}
// 类表达式
const Animal = class {}
//-------------------------------------需要注意的细节-------------------------------------
//1.类没有变量提升
console.log(Person)
class Person {}// Uncaught ReferenceError: Cannot access 'Person' before initialization
//2.类受块作用域限制
{
class ClassDeclaration {}
}
console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined
//3.类不可配置,不可枚举,不可重写
class Person {}
console.log(Object.getOwnPropertyDescriptor(Person, 'prototype'))//{value: {…}, writable: false, enumerable: false, configurable: false}
//4.Class 默认就是严格模式
复制代码
从各方面来看,Class
就是一种特殊函数。
class Person {}
// 使用 typeof 操作符检测 Person 是 function 类型。
console.log(Person) // class Person {}
console.log(typeof Person) // function
// Person 有 prototype 属性,原型对象也有 constructor 属性指向类自身。
console.log(Person.prototype) // { constructor: f() }
console.log(Person === Person.prototype.constructor) // true
// 使用 instanceof 操作符检查 Person 的原型在实例 p 的原型链上。
let p = new Person()
console.log(p instanceof Person) // true
复制代码
class
是 JavaScript 的一等公民,因此可以像其他对象或函数引用一样把类作为参数传递。
let classList = [
class {
constructor(id) {
this.id_ = id
console.log(`instance ${this.id_}`)
}
}
]
function createInstance(classDefinition, id) {
return new classDefinition(id)
}
let foo = createInstance(classList[0], 1024) // instance 1024
复制代码
与立即调用函数表达式相似,类也可以立即实例化:
// 因为是一个类表达式,所以类名是可选的
let p = new class Foo {
constructor(x) {
console.log(x)
}
}('bar') // bar
console.log(p) // Foo {}
复制代码
类的构成
类构造函数
constructor
关键字用于在class
定义块内部创建class
的构造函数。不定义constructor
相当于将构造函数定义为空函数。
class Person {}
//等价于
class Person {
constructor() {}
}
复制代码
使用 new
操作符实例化 Person
的操作意味着应该使用 constructor
函数进行实例化。在constructor
内部,可以为新创建的实例(this)添加属性。
class Person {
constructor(age, job) {
// 这个例子先使用对象包装类型定义一个字符串,为的是在下面测试两个对象的相等性。
this.name = new String("小刘")
this.age = age
this.sayName = () => console.log(this.name)
this.job = job
}
}
// 类实例化时传入的参数会用作构造函数的参数。
let p1 = new Person(18, "前端工程师")
console.log(p1.name, p1.age, p1.job) //小刘,18
p1.sayName() //小刘
let p2 = new Person(18, "前端工程师")
// 每个实例都对应一个唯一的成员对象,不会在原型上共享。
console.log(p1.name === p2.name) // false
复制代码
默认情况下,类构造函数会在执行之后返回 this 对象,在这里是undefined
(由于 class 内部是严格模式,所以 this 实际指向的是undefined
)。
原型方法
为了在实例间共享方法,Class
定义语法把在Class
块中定义的方法作为原型方法。
class Person {
constructor() {
// 添加到 this 的所有内容都会存在于不同的实例上
this.locate = () => console.log('instance')
}
// 在类块中定义的所有内容都会定义在类的原型上
locate() {
console.log('prototype')
}
// name: "小刘" //Uncaught SyntaxError: Unexpected identifier,不能在类块中给原型添加原始值或对象作为成员数据。
}
let p = new Person()
p.locate() // instance
Person.prototype.locate() // prototype
复制代码
类方法等同于对象属性,因此可以使用字符串、符号或计算的值作为键。
const symbolKey = Symbol('symbolKey')
class Person {
stringKey() {
console.log('invoked stringKey')
}
[symbolKey]() {
console.log('invoked symbolKey')
}
['computed' + 'Key']() {
console.log('invoked computedKey')
}
}
let p = new Person()
p.stringKey() // invoked stringKey
p[symbolKey]() // invoked symbolKey
p.computedKey() // invoked computedKey
复制代码
访问器
类定义也支持获取和设置访问器。
class Person {
set name(newName) {
this.name_ = newName
}
get name() {
return this.name_
}
}
let p = new Person
p.name = "小刘"
console.log(p.name) // 小刘
复制代码
静态方法
如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
- 静态成员每个类上只能有一个,多个同名静态成员会被覆盖,生效的只有最后一个。
- 静态方法的
this
指向Class
。 - 父类的静态方法子类可以继承。
super
方法可以调用父类上的静态方法。
class Person {
constructor() {
// 添加到 this 的所有内容都会存在于不同的实例上
this.locate = () => console.log('instance', this)
}
// 定义在类的原型对象上
locate() {
console.log('prototype', this)
}
// 定义在类本身上
static locate() {
console.log('class', this)
}
}
let p = new Person
p.locate() // instance, Person {}
Person.prototype.locate() // prototype, {constructor: ƒ, locate: ƒ}
Person.locate() // class, class Person {...},静态方法直接通过类标识符调用
复制代码
静态类方法非常适合作为实例工厂。
class Person {
constructor(age) {
this.age_ = age
}
sayAge() {
console.log(this.age_)
}
static create() {
// 使用随机年龄创建并返回一个 Person 实例
return new Person(Math.floor(Math.random() * 100))
}
}
console.log(Person.create()) // Person { age_: ... }
复制代码
迭代器与生成器方法
class Person {
// 在原型上定义生成器方法
* createNicknameIterator() {
yield "小刘"
yield "小孙"
yield "小李"
}
// 在类上定义生成器方法
static * createJobIterator() {
yield "前端工程师"
yield "后端工程师"
yield "全栈工程师"
}
}
let jobIter = Person.createJobIterator()
console.log(jobIter.next().value) // 前端工程师
console.log(jobIter.next().value) // 后端工程师
console.log(jobIter.next().value) // 全栈工程师
let p = new Person()
let nicknameIter = p.createNicknameIterator()
console.log(nicknameIter.next().value) // 小刘
console.log(nicknameIter.next().value) // 小孙
console.log(nicknameIter.next().value) // 小李
复制代码
因为支持生成器方法,所以可以通过添加一个默认的迭代器,把类实例变成可迭代对象。
class Person {
constructor() {
this.nicknames = ["小刘", "小孙", "小李"]
}
*[Symbol.iterator]() {
yield* this.nicknames.entries()
}
}
let p = new Person()
for (let [idx, nickname] of p) {
console.log(nickname)
}
//小刘
//小孙
//小李
复制代码
也可以只返回迭代器实例。
class Person {
constructor() {
this.nicknames = ["小刘", "小孙", "小李"]
}
[Symbol.iterator]() {
return this.nicknames.entries()
}
}
let p = new Person()
for (let [idx, nickname] of p) {
console.log(nickname)
}
复制代码
静态属性
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
以前只能在Class
外部定义,现在通过前缀static
关键字就可以实现。
class Person {
sayName() {
console.log(`${Person.greeting} ${this.name}`)
}
}
// 在类上定义数据成员
Person.greeting = "我的名字叫" // 在原型上定义数据成员
Person.prototype.name = "小刘"
let p = new Person()
p.sayName() // 我的名字叫 小刘
//--------------------------------------------------------------------
class Person {
sayName() {
console.log(`${Person.greeting} ${this.name}`)
}
static greeting = "我的名字叫"
}
// 在类上定义数据成员
Person.prototype.name = "小刘"
let p = new Person()
p.sayName() // 我的名字叫 小刘
复制代码
类的继承
ES6 类支持单继承。使用 extends
关键字,就可以继承任何拥有 [[Construct]] 和原型的对象。
class Vehicle {}
// 继承类
class Bus extends Vehicle {}
let b = new Bus()
console.log(b instanceof Bus) // true
console.log(b instanceof Vehicle) // true
function Person() {}
// 继承普通构造函数
class Engineer extends Person {}
let e = new Engineer()
console.log(e instanceof Engineer) // true
console.log(e instanceof Person) // true
复制代码
派生类都会通过原型链访问到类和原型上定义的方法。this
的值会反映调用相应方法的实例或者类。
class Vehicle {
identifyPrototype(id) {
console.log(id, this)
}
static identifyClass(id) {
console.log(id, this)
}
}
class Bus extends Vehicle {
constructor(){
super()
this.name = "宇通"
}
}
let v = new Vehicle()
let b = new Bus()
b.identifyPrototype('bus') // bus, Bus {name: "宇通"}
v.identifyPrototype('vehicle') // vehicle, Vehicle {}
Bus.identifyClass('bus') // bus, class Bus extends Vehicle {...}
Vehicle.identifyClass('vehicle') // vehicle, class Vehicle {...}
复制代码
extends 关键字也可以在类表达式中使用,因此
let Bar = class extends Foo {}
是有效的语法。
super()
派生类的方法可以通过 super
关键字引用它们的原型。这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。
class Vehicle {
constructor() {
this.hasEngine = true
}
static identify() {
console.log('vehicle')
}
}
class Bus extends Vehicle {
constructor() {
/**
* 不要在调用 super()之前引用 this,否则会抛出 ReferenceError。
* 这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,
* 然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
*/
super() // 相当于 super.constructor(),在类构造函数中使用 super 可以调用父类构造函数。
console.log(this instanceof Vehicle)
console.log(this)
}
static identify() {
super.identify() // 在静态方法中可以通过 super 调用父类上定义的静态方法。
}
}
new Bus()
// true
// Bus { hasEngine: true }
Bus.identify()
//vehicle
复制代码
注意:ES6 给类构造函数和静态方法添加了内部特性
[[HomeObject]]
,这个特性是一个指针,指向定义该方法的对象。这个指针是自动赋值的,而且只能在 JavaScript 引擎内部访问。super 始终会定义为[[HomeObject]]
的原型。
在使用super
时要注意几个问题。
super
只能在派生类构造函数和静态方法中使用。
//
class Vehicle {
constructor() {
super()
// Uncaught SyntaxError: 'super' keyword unexpected here
}
}
复制代码
不能单独引用 super
关键字,要么用它调用构造函数,要么用它引用静态方法。
class Vehicle {}
class Bus extends Vehicle {
constructor() {
console.log(super)// Uncaught SyntaxError: 'super' keyword unexpected here
}
}
复制代码
调用 super()
会调用父类构造函数,并将返回的实例赋值给 this
。
class Vehicle {}
class Bus extends Vehicle {
constructor() {
super()
console.log(this instanceof Vehicle)
}
}
new Bus() // true
复制代码
super()
的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。
class Vehicle {
constructor(licensePlate) {
this.licensePlate = licensePlate
}
}
class Bus extends Vehicle {
constructor(licensePlate) {
super(licensePlate)
}
}
console.log(new Bus('CODE999')) // Bus {licensePlate: "CODE999"}
复制代码
如果没有定义类构造函数,在实例化派生类时会调用 super()
,而且会传入所有传给派生类的参数。
class Vehicle {
constructor(licensePlate) {
this.licensePlate = licensePlate
}
}
class Bus extends Vehicle {}
console.log(new Bus('CODE999')) // Bus {licensePlate: "CODE999"}
复制代码
在类构造函数中,不能在调用 super()
之前引用 this
。
class Vehicle {}
class Bus extends Vehicle {
constructor() {
console.log(this)
}
}
new Bus()
// ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
复制代码
如果在派生类中显式定义了构造函数,则要么必须在其中调用 super()
,要么必须在其中返回一个对象。
class Vehicle {}
class Car extends Vehicle {}
class Bus extends Vehicle {
constructor() {
super()
}
}
class Van extends Vehicle {
constructor() {
return {}
}
}
console.log(new Car()) // Car {}
console.log(new Bus()) // Bus {}
console.log(new Van()) // {}
复制代码
抽象基类
new.target
保存通过 new
关键字调用的类或函数。通过在实例化时检测 new.target
是不是抽象基类,可以阻止对抽象基类的实例化。
// 抽象基类
class Vehicle {
constructor() {
console.log(new.target)
if (new.target === Vehicle) {
throw new Error('Vehicle cannot be directly instantiated')
}
}
}
// 派生类
class Bus extends Vehicle {}
new Bus() // class Bus {...},new.target 指向的是初始化类的类定义。
new Vehicle() // class Vehicle {...},new.target 指向的是初始化类的类定义。
// Error: Vehicle cannot be directly instantiated
复制代码
通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法。因为原型方法在调用类构造函数之前就已经存在了,所以可以通过 this 关键字来检查相应的方法。
// 抽象基类
class Vehicle {
constructor() {
if (new.target === Vehicle) {
throw new Error('Vehicle cannot be directly instantiated')
}
if (!this.foo) {
throw new Error('Inheriting class must define foo()')
}
console.log('success!')
}
}
// 派生类
class Bus extends Vehicle {
foo() {}
}
// 派生类
class Van extends Vehicle {}
new Bus() // success!
new Van() // Error: Inheriting class must define foo()
复制代码
继承内置类型
ES6 类为继承内置引用类型提供了顺畅的机制,开发者可以方便地扩展内置类型。
class SuperArray extends Array {
shuffle() {
// 洗牌算法
for (let i = this.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))[this[i], this[j]] = [this[j], this[i]]
}
}
}
let a = new SuperArray(1, 2, 3, 4, 5)
console.log(a instanceof Array) // true
console.log(a instanceof SuperArray) // true
console.log(a) // [1, 2, 3, 4, 5]
a.shuffle()
console.log(a) // [3, 1, 4, 5, 2]
复制代码
有些内置类型的方法会返回新实例。默认情况下,返回实例的类型与原始实例的类型是一致的。
class SuperArray extends Array {}
let a1 = new SuperArray(1, 2, 3, 4, 5)
let a2 = a1.filter(x => !!(x % 2)) //!!就是将其他类型都转换成boolean型
console.log(a1) // [1, 2, 3, 4, 5]
console.log(a2) // [1, 3, 5]
console.log(a1 instanceof SuperArray) // true
console.log(a2 instanceof SuperArray) // true
复制代码
如果想覆盖这个默认行为,则可以覆盖 Symbol.species
访问器,这个访问器决定在创建返回的实例时使用的类。
class SuperArray extends Array {
static get[Symbol.species]() {
return Array
}
}
let a1 = new SuperArray(1, 2, 3, 4, 5)
let a2 = a1.filter(x => !!(x % 2))
console.log(a1) // [1, 2, 3, 4, 5]
console.log(a2) // [1, 3, 5]
console.log(a1 instanceof SuperArray) // true
console.log(a2 instanceof SuperArray) // false
复制代码
类混入
把不同类的行为集中到一个类是一种常见的 JavaScript 模式。虽然 ES6 没有显式支持多类继承,但通过现有特性可以轻松地模拟这种行为。
注意
Object.assign()
方法是为了混入对象行为而设计的。只有在需要混入类的行为时才有必要自己实现混入表达式。如果只是需要混入多个对象的属性,那么使用Object.assign()
就可以了。
extends 关键字后可以跟表达式(可以解析为一个类或者一个构造函数)。这个表达式会在求值类定义时被求值。
class Vehicle {}
function getParentClass() {
console.log('evaluated expression')
return Vehicle
}
class Bus extends getParentClass() {}// evaluated expression(可求值的表达式)
复制代码
混入模式可以通过在一个表达式中连缀多个混入元素来实现,这个表达式最终会解析为一个可以被继承的类。
如果 Person 类需要组合 A、B、C,则需要某种机制实现 B 继承 A,C 继承 B,而 Person再继承 C,从而把 A、B、C 组合到这个超类中。实现这种模式有不同的策略。
一个策略是定义一组“可嵌套”的函数,每个函数分别接收一个超类作为参数,而将混入类定义为这个参数的子类,并返回这个类。这些组合函数可以连缀调用,最终组合成超类表达式:
class Vehicle {}
let FooMixin = (Superclass) => class extends Superclass {
foo() {
console.log('foo')
}
}
let BarMixin = (Superclass) => class extends Superclass {
bar() {
console.log('bar')
}
}
let BazMixin = (Superclass) => class extends Superclass {
baz() {
console.log('baz')
}
}
class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {}
let b = new Bus()
b.foo() // foo
b.bar() // bar
b.baz() // baz
复制代码
通过写一个辅助函数,可以把嵌套调用展开:
class Vehicle {}
let FooMixin = (Superclass) => class extends Superclass {
foo() {
console.log('foo')
}
}
let BarMixin = (Superclass) => class extends Superclass {
bar() {
console.log('bar')
}
}
let BazMixin = (Superclass) => class extends Superclass {
baz() {
console.log('baz')
}
}
function mix(BaseClass, ...Mixins) {
return Mixins.reduce((accumulator, current) => current(accumulator), BaseClass)
}
class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin) {}
let b = new Bus()
b.foo() // foo
b.bar() // bar
b.baz() // baz
复制代码
Class实现原理
Class
的底层实现原理可以通过Babel
来观察一下。
类的实现
类构造函数
//转换前
class Person {
constructor(name) {
this.name = name
this.sayName = () => {
console.log("我的名字叫" + this.name)
}
}
}
//转换后
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person(name) {
"use strict";
var _this = this;
_classCallCheck(this, Person);
this.name = name;
this.sayName = function () {
console.log("我的名字叫" + _this.name);
};
};
复制代码
-
先声明一个
_classCallCheck
方法,这个方法的作用是为了检查类是不是通过new
关键字调用的。如果不是(也就是直接调用Person()
),就抛出Cannot call a class as a function
错误。 -
类是严格模式(
use strict
)。 -
将
constructor
的属性和方法都赋值给this
(实例)。 -
执行
constructor
内部的逻辑。 -
return this
(默认情况下,类构造函数会在执行之后返回this
对象)。
原型方法和静态方法
//转换前
class Person {
//原型方法
sayAge() {
console.log("我今年 " + this.age)
}
//静态属性
static job = "前端工程师"
//静态方法
static sayJob() {
console.log("我的工作是 " + Person.job)
}
}
//原型属性
Person.prototype.age = 18
//转换后
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
// enumerable 默认为 false
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Person = /*#__PURE__*/ function () {
"use strict";
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "sayAge",
value: function sayAge() {
console.log("我今年 " + this.age);
}
}], [{
key: "sayJob",
value: function sayJob() {
console.log("我的工作是 " + Person.job);
}
}]);
return Person;
}();
_defineProperty(Person, "job", "前端工程师");
Person.prototype.age = 18;
复制代码
-
可以看到,此时的
Person
变成了一个IIFE
,IIFE
执行之后仍然返回Person
类,但内部还封装了一个constructor
构造函数和一个_createclass
函数。 -
构造函数
Person
,这个构造函数里面有我们检查是否用new
调用的_classCallCheck
方法。虽然上面没有显示调用constructor
,但是Class
默认会自动创建一个,这也就是书中所说的空constructor
。 -
_defineProperties
函数,它接收类(或者类的原型)和一个存放对象的数组做为参数,之后遍历数组中的每个对象,定义每个方法的特性,并将它们逐一添加到类(或者类的原型)上面。这里所涉及到的特性包括:enumberable
:该属性(方法)是否可枚举。如果方法本身已经定义了该特性,则使用该特性;如果没有定义,则定义该方法为不可枚举。configurable
:该属性(方法)是否可以配置。writable
:如果该属性是数据属性而不是访问器属性,那么会有一个value
,此时设置该属性为可写。
默认
enumerable
为false
,configurable
为true
,是为了防止Object.keys()
之类的方法遍历到。然后通过判断
value
是否存在,来判断是否是getter
和setter
。如果存在
value
,就为descriptor
添加value
和writable
属性,如果不存在,就直接使用get
和set
属性。 -
_createClass
函数,首先,它可以接收三个参数:- 第一个参数:类(这里是
Person
类); - 第二个参数:存放对象的数组,每个对象都是关于类的原型方法的特性描述对象(这里是
sayAge()
); - 第三个参数:存放对象的数组,每个对象都是关于类的静态方法的特性描述对象(这里是
sayJob()
)。
接着,它会依次检查是否有传入第二个和第三个参数,如果有,就调用
_defineProperties
函数,分别为类的原型定义原型方法,为类本身定义静态方法。 - 第一个参数:类(这里是
-
_defineProperty
函数,它接收类,属性名和属性值作为参数,如果指定的属性在类或类的原型链中,定义每个属性的特性,并将它们逐一添加到类上面;或者,重写类上的属性值。这里所涉及到的特性包括:enumberable
:该方法可枚举。configurable
:该方法可配置。writable
:该方法可重写。
最后返回类。
继承实现
//转换前
class Vehicle {
}
class Bus extends Vehicle {
constructor() {
super()
}
}
//转换后
require("core-js/modules/es.symbol.description.js");
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
//省略
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Vehicle = function Vehicle() {
"use strict";
_classCallCheck(this, Vehicle);
};
var Bus = /*#__PURE__*/ function (_Vehicle) {
"use strict";
_inherits(Bus, _Vehicle);
var _super = _createSuper(Bus);
function Bus() {
_classCallCheck(this, Bus);
return _super.call(this);
}
return Bus;
}(Vehicle);
复制代码
_typeof(obj)
函数是 一个工具函数,主要是为了对Symbol
进行正确的处理。- 它首先会检查当前环境是否支持原生的
Symbol
,如果支持就直接返回typeof obj
表达式的计算结果; - 如果不支持,再检查
obj
是不是通过 polyfill 实现的Symbol
的一个实例,如果是就返回它的类型(也就是返回"symbol"
),如果不是,就返回typeof obj
的计算结果。 - 在这里,这个函数假定了我们当前的环境是原生支持
Symbol
或者通过 polyfill 实现了支持的。
- 它首先会检查当前环境是否支持原生的
_setPrototypeOf(o, p)
函数检查当前环境是否支持调用Object.setPrototypeOf()
方法,如果不支持,就通过__proto__
手动给实例建立原型关系。_getPrototypeOf(o)
函数检查当前环境是否支持调用Object.setPrototypeOf()
方法,如果支持就调用Object.getPrototypeOf()
方法;如果不支持,就声明一个Object.setPrototypeOf()
函数,返回o.__proto__
。_possibleConstructorReturn(self,call)
这个辅助函数主要是用来实现super()
的效果,对应到寄生组合式继承上则是借用构造函数继承的部分,有所不同的是,该方法返回一个this
并赋给子类的this
。- 两个参数
self
代表构造函数的实例,call
代表构造函数的返回值; if
语句块里面的判断是检查call
的类型,当它是一个对象或者函数的时候,直接将其作为返回值;- 否则就返回
_assertThisInitialized(self)
。
- 两个参数
_assertThisInitialized(self)
调用这个方法的时候,我们期望的结果是this
已经得到初始化了。- 如果检查发现
this
是undefined
,就会抛出一个错误,提示我们由于没有调用super()
,所以无法得到this
。 - 否则就返回
this
。 - 为什么用
void 0
而不用undefined
是因为undefined
在IE低版本中能被重写。
- 如果检查发现
_isNativeReflectConstruct()
这个方法用于检测当前环境是否支持原生的Reflect
。- 使用
Reflect.construct
的主要原因是当调用来Reflect.construct()
创建对象,new.target
值会自动指定到target
(或者newTarget
,前提是newTarget
指定了)。 - 解决了使用
Super.apply
或者Object.create
会将构造函数内部的new.target
指向undefined
的问题。
- 使用
看一下最后面这段代码:
var Bus = /*#__PURE__*/ function (_Vehicle) {
"use strict";
_inherits(Bus, _Vehicle);
var _super = _createSuper(Bus);
function Bus() {
_classCallCheck(this, Bus);
return _super.call(this);
}
return Bus;
}(Vehicle);
复制代码
这里的Bus
同样是一个IIFE
,并且实际上也是返回一个Bus
子类结构函数,不同的是,它内部还封装了其它方法的调用。
-
_inherits(Bus, _Vehicle)
函数是实现继承的其中一个核心方法,可以说它的本质就是ES5中的寄生组合式继承。- 这个方法接收一个父类和子类作为参数
- 检查
extends
的继承目标(即父类),必须是函数或者是null
; - 使用
Object.create
,设置子类prototype
属性的__proto__
属性指向父类的prototype
属性; - 设置子类的
__proto__
属性指向父类(目的是让子类继承父类的静态方法)。
- 检查
- 这个方法接收一个父类和子类作为参数
-
_super.call(this)
函数,就是_createSuper(Derived)
函数,创建一个super()
,来调用父元素构造函数,主要是处理其返回类型。-
获取原型,也就是
_Vehicle
(在之前已经完成继承)。 -
检查当前环境是否支持
Reflect
。- 支持,执行
Reflect.construct(Super, arguments, NewTarget)
,最终返回一个基于Super
父构造函数的实例,相当于执行了new Super(...arguments)
。- 这个实例的
__proto__
的constructor
是NewTarget
,因此在某种程度上,你可以说这就是一个子类实例,不过它拥有父类实例的所有属性。
- 这个实例的
- 不支持,执行
Super.apply
(借用构造函数继承)。
- 支持,执行
-
检查返回值
-
此时
result
的结果可能是三种:- 一个继承了父类实例所有属性的子类实例;
- 可能是父类构造函数中自定义返回的一个非空对象;
- 可能是默认返回的
undefined
。
-
如果是第一种和第二种情况,直接返回
result
。 -
第三种情况,对当初传进去的子类实例(已经通过
Super.apply
进行了增强),也就是this
进行断言,然后返回出去。
-
-
参考:
阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版
ES6 系列之 Babel 是如何编译 Class 的(上)
ES6 系列之 Babel 是如何编译 Class 的(下)